Merge remote-tracking branch 'origin/master'

This commit is contained in:
wenyann 2020-12-09 16:49:41 +08:00
commit 5221ec796d
110 changed files with 2779 additions and 748 deletions

View File

@ -263,13 +263,20 @@
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<!-- swagger 解析 -->
<!-- swagger2 解析 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-parser</artifactId>
<version>1.0.51</version>
</dependency>
<!-- swagger3 解析 -->
<!--<dependency>-->
<!--<groupId>io.swagger.parser.v3</groupId>-->
<!--<artifactId>swagger-parser</artifactId>-->
<!--<version>2.0.24</version>-->
<!--</dependency>-->
<!-- 执行 js 代码依赖 -->
<dependency>
<groupId>org.graalvm.sdk</groupId>

View File

@ -81,5 +81,10 @@ public class ApiAutomationController {
apiAutomationService.run(request);
}
@PostMapping("/getReference")
public List<ApiScenario> getReference(@RequestBody ApiScenarioRequest request) {
return apiAutomationService.getReference(request);
}
}

View File

@ -4,6 +4,8 @@ import io.metersphere.base.domain.ApiTestReport;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class APIReportResult extends ApiTestReport {
@ -14,5 +16,7 @@ public class APIReportResult extends ApiTestReport {
private String userName;
private List<String> scenarioIds;
private String content;
}

View File

@ -15,5 +15,7 @@ public class RunScenarioRequest {
private String environmentId;
private String triggerMode;
private List<String> scenarioIds;
}

View File

@ -17,7 +17,10 @@ import io.metersphere.commons.utils.CommonBeanFactory;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import org.apache.jmeter.config.Arguments;
import java.util.LinkedList;
import java.util.List;
@ -40,11 +43,14 @@ public class MsScenario extends MsTestElement {
@JSONField(ordinal = 13)
private List<KeyValue> variables;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
if (environmentId != null) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environment = environmentService.get(environmentId);
config = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
config.setConfig(JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class));
}
if (CollectionUtils.isNotEmpty(this.getVariables())) {
config.setVariables(this.variables);
}
if (this.getReferenced() != null && this.getReferenced().equals("Deleted")) {
return;
@ -66,10 +72,29 @@ public class MsScenario extends MsTestElement {
ex.printStackTrace();
}
}
// 场景变量
if (CollectionUtils.isNotEmpty(this.getVariables())) {
tree.add(arguments());
}
if (CollectionUtils.isNotEmpty(hashTree)) {
for (MsTestElement el : hashTree) {
el.toHashTree(tree, el.getHashTree(), config);
}
}
}
private Arguments arguments() {
Arguments arguments = new Arguments();
arguments.setEnabled(true);
arguments.setName(name + "Variables");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
variables.stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
return arguments;
}
}

View File

@ -16,7 +16,6 @@ import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler;
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
import io.metersphere.api.dto.definition.request.timer.MsConstantTimer;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.commons.utils.LogUtil;
import lombok.Data;
import org.apache.jmeter.protocol.http.control.AuthManager;
@ -71,7 +70,7 @@ public class MsTestElement {
private LinkedList<MsTestElement> hashTree;
// 公共环境逐层传递如果自身有环境 以自身引用环境为准否则以公共环境作为请求环境
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
for (MsTestElement el : hashTree) {
el.toHashTree(tree, el.hashTree, config);
}
@ -95,7 +94,7 @@ public class MsTestElement {
return null;
}
public HashTree generateHashTree(EnvironmentConfig config) {
public HashTree generateHashTree(ParameterConfig config) {
HashTree jmeterTestPlanHashTree = new ListedHashTree();
this.toHashTree(jmeterTestPlanHashTree, this.hashTree, config);
return jmeterTestPlanHashTree;
@ -103,7 +102,7 @@ public class MsTestElement {
public HashTree generateHashTree() {
HashTree jmeterTestPlanHashTree = new ListedHashTree();
this.toHashTree(jmeterTestPlanHashTree, this.hashTree, null);
this.toHashTree(jmeterTestPlanHashTree, this.hashTree, new ParameterConfig());
return jmeterTestPlanHashTree;
}

View File

@ -1,7 +1,6 @@
package io.metersphere.api.dto.definition.request;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -19,7 +18,7 @@ import java.util.List;
public class MsTestPlan extends MsTestElement {
private String type = "TestPlan";
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
final HashTree testPlanTree = tree.add(getPlan());
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {

View File

@ -1,7 +1,6 @@
package io.metersphere.api.dto.definition.request;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -19,7 +18,7 @@ import java.util.List;
public class MsThreadGroup extends MsTestElement {
private String type = "ThreadGroup";
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
final HashTree groupTree = tree.add(getThreadGroup());
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {

View File

@ -0,0 +1,15 @@
package io.metersphere.api.dto.definition.request;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import lombok.Data;
import java.util.List;
@Data
public class ParameterConfig {
// 环境配置
private EnvironmentConfig config;
// 公共场景参数
private List<KeyValue> variables;
}

View File

@ -2,7 +2,7 @@ package io.metersphere.api.dto.definition.request.assertions;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -24,7 +24,7 @@ public class MsAssertions extends MsTestElement {
private MsAssertionDuration duration;
private String type = "Assertions";
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
addAssertions(tree);
}

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
@ -50,7 +51,7 @@ public class MsAuthManager extends MsTestElement {
@JSONField(ordinal = 18)
private String environment;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
AuthManager authManager = new AuthManager();
authManager.setEnabled(true);
authManager.setName(this.getUsername() + "AuthManager");
@ -63,8 +64,8 @@ public class MsAuthManager extends MsTestElement {
if (environment != null) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environmentWithBLOBs = environmentService.get(environment);
config = JSONObject.parseObject(environmentWithBLOBs.getConfig(), EnvironmentConfig.class);
this.url = config.getHttpConfig().getProtocol() + "://" + config.getHttpConfig().getSocket();
EnvironmentConfig envConfig = JSONObject.parseObject(environmentWithBLOBs.getConfig(), EnvironmentConfig.class);
this.url = envConfig.getHttpConfig().getProtocol() + "://" + envConfig.getHttpConfig().getSocket();
}
}
auth.setDomain(this.domain);

View File

@ -3,8 +3,8 @@ package io.metersphere.api.dto.definition.request.configurations;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -25,7 +25,7 @@ public class MsHeaderManager extends MsTestElement {
@JSONField(ordinal = 10)
private List<KeyValue> headers;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
HeaderManager headerManager = new HeaderManager();
headerManager.setEnabled(true);
headerManager.setName(this.getName() + "Headers");

View File

@ -2,7 +2,7 @@ package io.metersphere.api.dto.definition.request.controller;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -25,7 +25,7 @@ public class MsIfController extends MsTestElement {
private String operator;
private String value;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
final HashTree groupTree = tree.add(ifController());
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {

View File

@ -2,6 +2,7 @@ package io.metersphere.api.dto.definition.request.dns;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.scenario.environment.Host;
@ -23,7 +24,7 @@ import java.util.List;
@JSONType(typeName = "DNSCacheManager")
public class MsDNSCacheManager extends MsTestElement {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
for (MsTestElement el : hashTree) {
el.toHashTree(tree, el.getHashTree(), config);
}

View File

@ -2,7 +2,7 @@ package io.metersphere.api.dto.definition.request.extract;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -24,7 +24,7 @@ public class MsExtract extends MsTestElement {
private List<MsExtractXPath> xpath;
private String type = "Extract";
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
addRequestExtractors(tree);
}

View File

@ -3,7 +3,7 @@ package io.metersphere.api.dto.definition.request.processors;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -26,7 +26,7 @@ public class MsJSR223Processor extends MsTestElement {
@JSONField(ordinal = 11)
private String scriptLanguage;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
JSR223Sampler processor = new JSR223Sampler();
processor.setEnabled(true);
processor.setName(this.getName() + "JSR223Processor");

View File

@ -3,7 +3,7 @@ package io.metersphere.api.dto.definition.request.processors.post;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -27,7 +27,7 @@ public class MsJSR223PostProcessor extends MsTestElement {
private String scriptLanguage;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
JSR223PostProcessor processor = new JSR223PostProcessor();
processor.setEnabled(true);
processor.setName(this.getName() + "JSR223PostProcessor");

View File

@ -3,7 +3,7 @@ package io.metersphere.api.dto.definition.request.processors.pre;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -26,7 +26,7 @@ public class MsJSR223PreProcessor extends MsTestElement {
@JSONField(ordinal = 11)
private String scriptLanguage;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
JSR223PreProcessor processor = new JSR223PreProcessor();
processor.setEnabled(true);
processor.setName(this.getName() + "JSR223PreProcessor");

View File

@ -7,11 +7,11 @@ import io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample;
import io.github.ningyu.jmeter.plugin.dubbo.sample.MethodArgument;
import io.github.ningyu.jmeter.plugin.util.Constants;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.definition.request.sampler.dubbo.MsConfigCenter;
import io.metersphere.api.dto.definition.request.sampler.dubbo.MsConsumerAndService;
import io.metersphere.api.dto.definition.request.sampler.dubbo.MsRegistryCenter;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -51,7 +51,7 @@ public class MsDubboSampler extends MsTestElement {
@JSONField(ordinal = 59)
private List<KeyValue> attachmentArgs;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
final HashTree testPlanTree = new ListedHashTree();
testPlanTree.add(dubboConfig());
tree.set(dubboSample(), testPlanTree);

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
@ -27,7 +28,11 @@ import org.apache.jorphan.collections.HashTree;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Data
@EqualsAndHashCode(callSuper = true)
@ -80,7 +85,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
@JSONField(ordinal = 24)
private List<KeyValue> arguments;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
HTTPSamplerProxy sampler = new HTTPSamplerProxy();
sampler.setEnabled(true);
sampler.setName(this.getName());
@ -96,23 +101,25 @@ public class MsHTTPSamplerProxy extends MsTestElement {
if (useEnvironment != null) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environment = environmentService.get(useEnvironment);
config = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
config.setConfig(JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class));
}
try {
if (config != null) {
if (config != null && config.getConfig() != null) {
String url = "";
sampler.setDomain(config.getHttpConfig().getDomain());
sampler.setPort(config.getHttpConfig().getPort());
sampler.setProtocol(config.getHttpConfig().getProtocol());
url = config.getHttpConfig().getProtocol() + "://" + config.getHttpConfig().getSocket();
sampler.setDomain(config.getConfig().getHttpConfig().getDomain());
sampler.setPort(config.getConfig().getHttpConfig().getPort());
sampler.setProtocol(config.getConfig().getHttpConfig().getProtocol());
url = config.getConfig().getHttpConfig().getProtocol() + "://" + config.getConfig().getHttpConfig().getSocket();
URL urlObject = new URL(url);
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
if (StringUtils.isNotBlank(this.getPath())) {
envPath += this.getPath();
}
if (CollectionUtils.isNotEmpty(this.getRest()) && this.isRest()) {
sampler.setPath(getRestParameters(URLDecoder.decode(envPath, "UTF-8")));
} else {
envPath = getRestParameters(URLDecoder.decode(envPath, "UTF-8"));
sampler.setPath(envPath);
}
if (CollectionUtils.isNotEmpty(this.getArguments())) {
sampler.setPath(getPostQueryParameters(URLDecoder.decode(envPath, "UTF-8")));
}
} else {
@ -125,18 +132,24 @@ public class MsHTTPSamplerProxy extends MsTestElement {
sampler.setPort(urlObject.getPort());
sampler.setProtocol(urlObject.getProtocol());
sampler.setPath(getRestParameters(URLDecoder.decode(urlObject.getPath(), "UTF-8")));
sampler.setPath(getPostQueryParameters(URLDecoder.decode(urlObject.getPath(), "UTF-8")));
if (CollectionUtils.isNotEmpty(this.getRest()) && this.isRest()) {
sampler.setPath(getRestParameters(URLDecoder.decode(urlObject.getPath(), "UTF-8")));
}
if (CollectionUtils.isNotEmpty(this.getArguments())) {
sampler.setPath(getPostQueryParameters(URLDecoder.decode(urlObject.getPath(), "UTF-8")));
}
}
} catch (Exception e) {
LogUtil.error(e);
}
// REST参数
if (CollectionUtils.isNotEmpty(this.getArguments())) {
sampler.setArguments(httpArguments(this.getRest()));
}
// 请求参数
if (CollectionUtils.isNotEmpty(this.getArguments())) {
sampler.setArguments(httpArguments(this.getArguments()));
}
// 请求体
if (!StringUtils.equals(this.getMethod(), "GET")) {
List<KeyValue> bodyParams = this.body.getBodyParams(sampler, this.getId());
@ -148,9 +161,10 @@ public class MsHTTPSamplerProxy extends MsTestElement {
setHeader(httpSamplerTree);
}
//判断是否要开启DNS
if (config != null && config.getCommonConfig() != null && config.getCommonConfig().isEnableHost()) {
MsDNSCacheManager.addEnvironmentVariables(httpSamplerTree, this.getName(), config);
MsDNSCacheManager.addEnvironmentDNS(httpSamplerTree, this.getName(), config);
if (config != null && config.getConfig() != null && config.getConfig().getCommonConfig() != null
&& config.getConfig().getCommonConfig().isEnableHost()) {
MsDNSCacheManager.addEnvironmentVariables(httpSamplerTree, this.getName(), config.getConfig());
MsDNSCacheManager.addEnvironmentDNS(httpSamplerTree, this.getName(), config.getConfig());
}
if (CollectionUtils.isNotEmpty(hashTree)) {
for (MsTestElement el : hashTree) {
@ -163,10 +177,22 @@ public class MsHTTPSamplerProxy extends MsTestElement {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(path);
stringBuffer.append("/");
Map<String, String> keyValueMap = new HashMap<>();
this.getRest().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).forEach(keyValue ->
stringBuffer.append(keyValue.getValue()).append("/")
keyValueMap.put(keyValue.getName(), keyValue.getValue())
);
return stringBuffer.substring(0, stringBuffer.length() - 1);
Pattern p = Pattern.compile("(\\{)([\\w]+)(\\})");
Matcher m = p.matcher(path);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String group = m.group(2);
//替换并且把替换好的值放到sb中
m.appendReplacement(sb, keyValueMap.get(group));
}
//把符合的数据追加到sb尾
m.appendTail(sb);
return sb.toString();
}
private String getPostQueryParameters(String path) {
@ -209,4 +235,5 @@ public class MsHTTPSamplerProxy extends MsTestElement {
private boolean isRest() {
return this.getRest().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).toArray().length > 0;
}
}

View File

@ -3,9 +3,9 @@ package io.metersphere.api.dto.definition.request.sampler;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.scenario.DatabaseConfig;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -39,7 +39,7 @@ public class MsJDBCSampler extends MsTestElement {
@JSONField(ordinal = 16)
private String environmentId;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
final HashTree samplerHashTree = tree.add(jdbcSampler());
tree.add(jdbcDataSource());
tree.add(arguments(this.getName() + " Variables", this.getVariables()));

View File

@ -3,7 +3,7 @@ package io.metersphere.api.dto.definition.request.sampler;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -49,7 +49,7 @@ public class MsTCPSampler extends MsTestElement {
@JSONField(ordinal = 23)
private String request;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
final HashTree samplerHashTree = new ListedHashTree();
samplerHashTree.add(tcpConfig());
tree.set(tcpSampler(), samplerHashTree);

View File

@ -3,7 +3,7 @@ package io.metersphere.api.dto.definition.request.timer;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
@ -26,7 +26,7 @@ public class MsConstantTimer extends MsTestElement {
@JSONField(ordinal = 12)
private String delay;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
final HashTree groupTree = tree.add(constantTimer());
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {

View File

@ -229,7 +229,11 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
if (StringUtils.equals("Error", report.getStatus())) {
event = NoticeConstants.Event.EXECUTE_FAILED;
}
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("testName", report.getName());
paramMap.put("id", report.getId());
paramMap.put("type", "performance");
paramMap.put("url", baseSystemConfigDTO.getUrl());
NoticeModel noticeModel = NoticeModel.builder()
.successContext(successContext)
.successMailTemplate("ApiSuccessfulNotification")
@ -239,6 +243,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
.status(report.getStatus())
.event(event)
.subject(subject)
.paramMap(paramMap)
.build();
noticeSendService.send(report.getTriggerMode(), noticeModel);
}

View File

@ -9,9 +9,8 @@ import com.google.gson.Gson;
import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.automation.*;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
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.dto.definition.request.*;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.base.domain.*;
@ -20,6 +19,7 @@ import io.metersphere.base.mapper.ApiTagMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
@ -219,7 +219,7 @@ public class ApiAutomationService {
}
}
private void createAPIReportResult(String id) {
private void createAPIReportResult(String id, String triggerMode) {
APIReportResult report = new APIReportResult();
report.setId(id);
report.setTestId(id);
@ -229,6 +229,7 @@ public class ApiAutomationService {
report.setUpdateTime(System.currentTimeMillis());
report.setStatus(APITestStatus.Running.name());
report.setUserId(SessionUtils.getUserId());
report.setTriggerMode(triggerMode);
apiReportService.addResult(report);
}
@ -244,7 +245,6 @@ public class ApiAutomationService {
MsTestPlan testPlan = new MsTestPlan();
testPlan.setHashTree(new LinkedList<>());
HashTree jmeterTestPlanHashTree = new ListedHashTree();
EnvironmentConfig config = null;
for (ApiScenario item : apiScenarios) {
MsThreadGroup group = new MsThreadGroup();
group.setLabel(item.getName());
@ -253,25 +253,29 @@ public class ApiAutomationService {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JSONObject element = JSON.parseObject(item.getScenarioDefinition());
String environmentId = element.getString("environmentId");
if (environmentId != null) {
ApiTestEnvironmentWithBLOBs environment = environmentService.get(environmentId);
config = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
}
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"), new TypeReference<LinkedList<MsTestElement>>() {
});
group.setHashTree(elements);
MsScenario scenario = JSONObject.parseObject(item.getScenarioDefinition(), MsScenario.class);
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
new TypeReference<LinkedList<MsTestElement>>() {
});
LinkedList<KeyValue> variables = mapper.readValue(element.getString("variables"),
new TypeReference<LinkedList<KeyValue>>() {
});
scenario.setHashTree(elements);
scenario.setVariables(variables);
LinkedList<MsTestElement> scenarios = new LinkedList<>();
scenarios.add(scenario);
group.setHashTree(scenarios);
testPlan.getHashTree().add(group);
} catch (Exception ex) {
ex.printStackTrace();
}
}
testPlan.toHashTree(jmeterTestPlanHashTree, testPlan.getHashTree(), config);
testPlan.toHashTree(jmeterTestPlanHashTree, testPlan.getHashTree(), new ParameterConfig());
// 调用执行方法
jMeterService.runDefinition(request.getId(), jmeterTestPlanHashTree, request.getReportId(), ApiRunMode.SCENARIO.name());
createAPIReportResult(request.getId());
createAPIReportResult(request.getId(), request.getTriggerMode() == null ? ReportTriggerMode.API.name() : request.getTriggerMode());
return request.getId();
}
@ -286,17 +290,23 @@ public class ApiAutomationService {
public String run(RunDefinitionRequest request, List<MultipartFile> bodyFiles) {
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
createBodyFiles(bodyUploadIds, bodyFiles);
EnvironmentConfig config = null;
EnvironmentConfig envConfig = null;
if (request.getEnvironmentId() != null) {
ApiTestEnvironmentWithBLOBs environment = environmentService.get(request.getEnvironmentId());
config = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
envConfig = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
}
ParameterConfig config = new ParameterConfig();
config.setConfig(envConfig);
HashTree hashTree = request.getTestElement().generateHashTree(config);
request.getTestElement().getJmx(hashTree);
// 调用执行方法
jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.SCENARIO.name());
createAPIReportResult(request.getId());
createAPIReportResult(request.getId(), ReportTriggerMode.MANUAL.name());
return request.getId();
}
public List<ApiScenario> getReference(ApiScenarioRequest request) {
return extApiScenarioMapper.selectReference(request);
}
}

View File

@ -1,5 +1,6 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.DeleteAPIReportRequest;
@ -7,6 +8,7 @@ import io.metersphere.api.dto.QueryAPIReportRequest;
import io.metersphere.api.dto.automation.APIScenarioReportResult;
import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiScenarioMapper;
import io.metersphere.base.mapper.ApiScenarioReportDetailMapper;
import io.metersphere.base.mapper.ApiScenarioReportMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioReportMapper;
@ -21,6 +23,7 @@ import sun.security.util.Cache;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.List;
import java.util.UUID;
@ -35,6 +38,8 @@ public class ApiScenarioReportService {
private ApiScenarioReportMapper apiScenarioReportMapper;
@Resource
private ApiScenarioReportDetailMapper apiScenarioReportDetailMapper;
@Resource
private ApiScenarioMapper apiScenarioMapper;
public void complete(TestResult result) {
Object obj = cache.get(result.getTestId());
@ -139,6 +144,27 @@ public class ApiScenarioReportService {
public String add(APIScenarioReportResult test) {
ApiScenarioReport report = createReport(test);
ApiScenarioReportDetail detail = new ApiScenarioReportDetail();
TestResult result = JSON.parseObject(test.getContent(), TestResult.class);
// 更新场景
if (result != null) {
result.getScenarios().forEach(item -> {
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andNameEqualTo(item.getName()).andProjectIdEqualTo(test.getProjectId());
List<ApiScenario> list = apiScenarioMapper.selectByExample(example);
if (list.size() > 0) {
ApiScenario scenario = list.get(0);
if (item.getError() > 0) {
scenario.setLastResult("Fail");
} else {
scenario.setLastResult("Success");
}
String passRate = new DecimalFormat("0%").format((float) item.getSuccess() / (item.getSuccess() + item.getError()));
scenario.setPassRate(passRate);
scenario.setReportId(report.getId());
apiScenarioMapper.updateByPrimaryKey(scenario);
}
});
}
detail.setContent(test.getContent().getBytes(StandardCharsets.UTF_8));
detail.setReportId(report.getId());
detail.setProjectId(test.getProjectId());

View File

@ -37,6 +37,12 @@ public class ApiScenario implements Serializable {
private Long updateTime;
private String passRate;
private String lastResult;
private String reportId;
private String scenarioDefinition;
private static final long serialVersionUID = 1L;

View File

@ -1193,6 +1193,216 @@ public class ApiScenarioExample {
addCriterion("update_time not between", value1, value2, "updateTime");
return (Criteria) this;
}
public Criteria andPassRateIsNull() {
addCriterion("pass_rate is null");
return (Criteria) this;
}
public Criteria andPassRateIsNotNull() {
addCriterion("pass_rate is not null");
return (Criteria) this;
}
public Criteria andPassRateEqualTo(String value) {
addCriterion("pass_rate =", value, "passRate");
return (Criteria) this;
}
public Criteria andPassRateNotEqualTo(String value) {
addCriterion("pass_rate <>", value, "passRate");
return (Criteria) this;
}
public Criteria andPassRateGreaterThan(String value) {
addCriterion("pass_rate >", value, "passRate");
return (Criteria) this;
}
public Criteria andPassRateGreaterThanOrEqualTo(String value) {
addCriterion("pass_rate >=", value, "passRate");
return (Criteria) this;
}
public Criteria andPassRateLessThan(String value) {
addCriterion("pass_rate <", value, "passRate");
return (Criteria) this;
}
public Criteria andPassRateLessThanOrEqualTo(String value) {
addCriterion("pass_rate <=", value, "passRate");
return (Criteria) this;
}
public Criteria andPassRateLike(String value) {
addCriterion("pass_rate like", value, "passRate");
return (Criteria) this;
}
public Criteria andPassRateNotLike(String value) {
addCriterion("pass_rate not like", value, "passRate");
return (Criteria) this;
}
public Criteria andPassRateIn(List<String> values) {
addCriterion("pass_rate in", values, "passRate");
return (Criteria) this;
}
public Criteria andPassRateNotIn(List<String> values) {
addCriterion("pass_rate not in", values, "passRate");
return (Criteria) this;
}
public Criteria andPassRateBetween(String value1, String value2) {
addCriterion("pass_rate between", value1, value2, "passRate");
return (Criteria) this;
}
public Criteria andPassRateNotBetween(String value1, String value2) {
addCriterion("pass_rate not between", value1, value2, "passRate");
return (Criteria) this;
}
public Criteria andLastResultIsNull() {
addCriterion("last_result is null");
return (Criteria) this;
}
public Criteria andLastResultIsNotNull() {
addCriterion("last_result is not null");
return (Criteria) this;
}
public Criteria andLastResultEqualTo(String value) {
addCriterion("last_result =", value, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultNotEqualTo(String value) {
addCriterion("last_result <>", value, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultGreaterThan(String value) {
addCriterion("last_result >", value, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultGreaterThanOrEqualTo(String value) {
addCriterion("last_result >=", value, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultLessThan(String value) {
addCriterion("last_result <", value, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultLessThanOrEqualTo(String value) {
addCriterion("last_result <=", value, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultLike(String value) {
addCriterion("last_result like", value, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultNotLike(String value) {
addCriterion("last_result not like", value, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultIn(List<String> values) {
addCriterion("last_result in", values, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultNotIn(List<String> values) {
addCriterion("last_result not in", values, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultBetween(String value1, String value2) {
addCriterion("last_result between", value1, value2, "lastResult");
return (Criteria) this;
}
public Criteria andLastResultNotBetween(String value1, String value2) {
addCriterion("last_result not between", value1, value2, "lastResult");
return (Criteria) this;
}
public Criteria andReportIdIsNull() {
addCriterion("report_id is null");
return (Criteria) this;
}
public Criteria andReportIdIsNotNull() {
addCriterion("report_id is not null");
return (Criteria) this;
}
public Criteria andReportIdEqualTo(String value) {
addCriterion("report_id =", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdNotEqualTo(String value) {
addCriterion("report_id <>", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdGreaterThan(String value) {
addCriterion("report_id >", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdGreaterThanOrEqualTo(String value) {
addCriterion("report_id >=", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdLessThan(String value) {
addCriterion("report_id <", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdLessThanOrEqualTo(String value) {
addCriterion("report_id <=", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdLike(String value) {
addCriterion("report_id like", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdNotLike(String value) {
addCriterion("report_id not like", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdIn(List<String> values) {
addCriterion("report_id in", values, "reportId");
return (Criteria) this;
}
public Criteria andReportIdNotIn(List<String> values) {
addCriterion("report_id not in", values, "reportId");
return (Criteria) this;
}
public Criteria andReportIdBetween(String value1, String value2) {
addCriterion("report_id between", value1, value2, "reportId");
return (Criteria) this;
}
public Criteria andReportIdNotBetween(String value1, String value2) {
addCriterion("report_id not between", value1, value2, "reportId");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -1,8 +1,7 @@
package io.metersphere.base.domain;
import lombok.Data;
import java.io.Serializable;
import lombok.Data;
@Data
public class ApiScenarioReport implements Serializable {

View File

@ -1,8 +1,9 @@
package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data;
import java.io.Serializable;
@Data
public class MessageTask implements Serializable {
private String id;
@ -27,5 +28,7 @@ public class MessageTask implements Serializable {
private Long createTime;
private String template;
private static final long serialVersionUID = 1L;
}

View File

@ -29,5 +29,7 @@ public class User implements Serializable {
private String source;
private String lastProjectId;
private static final long serialVersionUID = 1L;
}

View File

@ -923,6 +923,76 @@ public class UserExample {
addCriterion("`source` not between", value1, value2, "source");
return (Criteria) this;
}
public Criteria andLastProjectIdIsNull() {
addCriterion("last_project_id is null");
return (Criteria) this;
}
public Criteria andLastProjectIdIsNotNull() {
addCriterion("last_project_id is not null");
return (Criteria) this;
}
public Criteria andLastProjectIdEqualTo(String value) {
addCriterion("last_project_id =", value, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdNotEqualTo(String value) {
addCriterion("last_project_id <>", value, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdGreaterThan(String value) {
addCriterion("last_project_id >", value, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdGreaterThanOrEqualTo(String value) {
addCriterion("last_project_id >=", value, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdLessThan(String value) {
addCriterion("last_project_id <", value, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdLessThanOrEqualTo(String value) {
addCriterion("last_project_id <=", value, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdLike(String value) {
addCriterion("last_project_id like", value, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdNotLike(String value) {
addCriterion("last_project_id not like", value, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdIn(List<String> values) {
addCriterion("last_project_id in", values, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdNotIn(List<String> values) {
addCriterion("last_project_id not in", values, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdBetween(String value1, String value2) {
addCriterion("last_project_id between", value1, value2, "lastProjectId");
return (Criteria) this;
}
public Criteria andLastProjectIdNotBetween(String value1, String value2) {
addCriterion("last_project_id not between", value1, value2, "lastProjectId");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -15,7 +15,7 @@
</delete>
<select id="selectAll" resultType="io.metersphere.api.dto.ApiMonitorSearch">
select distinct url from api_data_view;
select distinct url from api_data_view order by url;
</select>
<select id="selectResponseTimeByUrl" parameterType="java.lang.String"
@ -56,7 +56,7 @@
</select>
<select id="selectReportIdByUrlAndStartTime" resultType="java.lang.String">
select report_id from api_data_view where response_code != 200 and url=#{apiUrl} and start_time=#{startTime};
select report_id from api_data_view where url=#{apiUrl} and start_time=#{startTime};
</select>
<insert id="insertListApiData" parameterType="java.util.List">

View File

@ -18,6 +18,9 @@
<result column="description" jdbcType="VARCHAR" property="description" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="pass_rate" jdbcType="VARCHAR" property="passRate" />
<result column="last_result" jdbcType="VARCHAR" property="lastResult" />
<result column="report_id" jdbcType="VARCHAR" property="reportId" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.ApiScenario">
<result column="scenario_definition" jdbcType="LONGVARCHAR" property="scenarioDefinition" />
@ -83,7 +86,7 @@
<sql id="Base_Column_List">
id, project_id, tag_id, user_id, api_scenario_module_id, module_path, `name`, `level`,
`status`, principal, step_total, follow_people, schedule, description, create_time,
update_time
update_time, pass_rate, last_result, report_id
</sql>
<sql id="Blob_Column_List">
scenario_definition
@ -142,13 +145,15 @@
`name`, `level`, `status`,
principal, step_total, follow_people,
schedule, description, create_time,
update_time, scenario_definition)
update_time, pass_rate, last_result,
report_id, scenario_definition)
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{tagId,jdbcType=VARCHAR},
#{userId,jdbcType=VARCHAR}, #{apiScenarioModuleId,jdbcType=VARCHAR}, #{modulePath,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR}, #{level,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR},
#{principal,jdbcType=VARCHAR}, #{stepTotal,jdbcType=INTEGER}, #{followPeople,jdbcType=VARCHAR},
#{schedule,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT},
#{updateTime,jdbcType=BIGINT}, #{scenarioDefinition,jdbcType=LONGVARCHAR})
#{updateTime,jdbcType=BIGINT}, #{passRate,jdbcType=VARCHAR}, #{lastResult,jdbcType=VARCHAR},
#{reportId,jdbcType=VARCHAR}, #{scenarioDefinition,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiScenario">
insert into api_scenario
@ -201,6 +206,15 @@
<if test="updateTime != null">
update_time,
</if>
<if test="passRate != null">
pass_rate,
</if>
<if test="lastResult != null">
last_result,
</if>
<if test="reportId != null">
report_id,
</if>
<if test="scenarioDefinition != null">
scenario_definition,
</if>
@ -254,6 +268,15 @@
<if test="updateTime != null">
#{updateTime,jdbcType=BIGINT},
</if>
<if test="passRate != null">
#{passRate,jdbcType=VARCHAR},
</if>
<if test="lastResult != null">
#{lastResult,jdbcType=VARCHAR},
</if>
<if test="reportId != null">
#{reportId,jdbcType=VARCHAR},
</if>
<if test="scenarioDefinition != null">
#{scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
@ -316,6 +339,15 @@
<if test="record.updateTime != null">
update_time = #{record.updateTime,jdbcType=BIGINT},
</if>
<if test="record.passRate != null">
pass_rate = #{record.passRate,jdbcType=VARCHAR},
</if>
<if test="record.lastResult != null">
last_result = #{record.lastResult,jdbcType=VARCHAR},
</if>
<if test="record.reportId != null">
report_id = #{record.reportId,jdbcType=VARCHAR},
</if>
<if test="record.scenarioDefinition != null">
scenario_definition = #{record.scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
@ -342,6 +374,9 @@
description = #{record.description,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
pass_rate = #{record.passRate,jdbcType=VARCHAR},
last_result = #{record.lastResult,jdbcType=VARCHAR},
report_id = #{record.reportId,jdbcType=VARCHAR},
scenario_definition = #{record.scenarioDefinition,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -364,7 +399,10 @@
schedule = #{record.schedule,jdbcType=VARCHAR},
description = #{record.description,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT}
update_time = #{record.updateTime,jdbcType=BIGINT},
pass_rate = #{record.passRate,jdbcType=VARCHAR},
last_result = #{record.lastResult,jdbcType=VARCHAR},
report_id = #{record.reportId,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -417,6 +455,15 @@
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=BIGINT},
</if>
<if test="passRate != null">
pass_rate = #{passRate,jdbcType=VARCHAR},
</if>
<if test="lastResult != null">
last_result = #{lastResult,jdbcType=VARCHAR},
</if>
<if test="reportId != null">
report_id = #{reportId,jdbcType=VARCHAR},
</if>
<if test="scenarioDefinition != null">
scenario_definition = #{scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
@ -440,6 +487,9 @@
description = #{description,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
pass_rate = #{passRate,jdbcType=VARCHAR},
last_result = #{lastResult,jdbcType=VARCHAR},
report_id = #{reportId,jdbcType=VARCHAR},
scenario_definition = #{scenarioDefinition,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
@ -459,7 +509,10 @@
schedule = #{schedule,jdbcType=VARCHAR},
description = #{description,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}
update_time = #{updateTime,jdbcType=BIGINT},
pass_rate = #{passRate,jdbcType=VARCHAR},
last_result = #{lastResult,jdbcType=VARCHAR},
report_id = #{reportId,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -2,9 +2,10 @@ package io.metersphere.base.mapper;
import io.metersphere.base.domain.MessageTask;
import io.metersphere.base.domain.MessageTaskExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface MessageTaskMapper {
long countByExample(MessageTaskExample example);
@ -16,15 +17,21 @@ public interface MessageTaskMapper {
int insertSelective(MessageTask record);
List<MessageTask> selectByExampleWithBLOBs(MessageTaskExample example);
List<MessageTask> selectByExample(MessageTaskExample example);
MessageTask selectByPrimaryKey(String id);
int updateByExampleSelective(@Param("record") MessageTask record, @Param("example") MessageTaskExample example);
int updateByExampleWithBLOBs(@Param("record") MessageTask record, @Param("example") MessageTaskExample example);
int updateByExample(@Param("record") MessageTask record, @Param("example") MessageTaskExample example);
int updateByPrimaryKeySelective(MessageTask record);
int updateByPrimaryKeyWithBLOBs(MessageTask record);
int updateByPrimaryKey(MessageTask record);
}

View File

@ -2,17 +2,20 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.MessageTaskMapper">
<resultMap id="BaseResultMap" type="io.metersphere.base.domain.MessageTask">
<id column="id" jdbcType="VARCHAR" property="id"/>
<result column="type" jdbcType="VARCHAR" property="type"/>
<result column="event" jdbcType="VARCHAR" property="event"/>
<result column="user_id" jdbcType="VARCHAR" property="userId"/>
<result column="task_type" jdbcType="VARCHAR" property="taskType"/>
<result column="webhook" jdbcType="VARCHAR" property="webhook"/>
<result column="identification" jdbcType="VARCHAR" property="identification"/>
<result column="is_set" jdbcType="BIT" property="isSet"/>
<result column="organization_id" jdbcType="VARCHAR" property="organizationId"/>
<result column="test_id" jdbcType="VARCHAR" property="testId"/>
<result column="create_time" jdbcType="BIGINT" property="createTime"/>
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="type" jdbcType="VARCHAR" property="type" />
<result column="event" jdbcType="VARCHAR" property="event" />
<result column="user_id" jdbcType="VARCHAR" property="userId" />
<result column="task_type" jdbcType="VARCHAR" property="taskType" />
<result column="webhook" jdbcType="VARCHAR" property="webhook" />
<result column="identification" jdbcType="VARCHAR" property="identification" />
<result column="is_set" jdbcType="BIT" property="isSet" />
<result column="organization_id" jdbcType="VARCHAR" property="organizationId" />
<result column="test_id" jdbcType="VARCHAR" property="testId" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.MessageTask">
<result column="template" jdbcType="LONGVARCHAR" property="template" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -25,13 +28,13 @@
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
AND ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
AND ${criterion.condition} #{criterion.value} AND #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
AND ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
@ -76,6 +79,25 @@
id, `type`, event, user_id, task_type, webhook, identification, is_set, organization_id,
test_id, create_time
</sql>
<sql id="Blob_Column_List">
`template`
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.MessageTaskExample" resultMap="ResultMapWithBLOBs">
SELECT
<if test="distinct">
DISTINCT
</if>
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
FROM message_task
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
ORDER BY ${orderByClause}
</if>
</select>
<select id="selectByExample" parameterType="io.metersphere.base.domain.MessageTaskExample" resultMap="BaseResultMap">
select
<if test="distinct">
@ -90,9 +112,11 @@
order by ${orderByClause}
</if>
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="ResultMapWithBLOBs">
select
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from message_task
where id = #{id,jdbcType=VARCHAR}
</select>
@ -107,14 +131,16 @@
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.MessageTask">
insert into message_task (id, `type`, event,
user_id, task_type, webhook,
identification, is_set, organization_id,
test_id, create_time)
values (#{id,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR}, #{event,jdbcType=VARCHAR},
#{userId,jdbcType=VARCHAR}, #{taskType,jdbcType=VARCHAR}, #{webhook,jdbcType=VARCHAR},
#{identification,jdbcType=VARCHAR}, #{isSet,jdbcType=BIT}, #{organizationId,jdbcType=VARCHAR},
#{testId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT})
INSERT INTO message_task (id, `type`, event,
user_id, task_type, webhook,
identification, is_set, organization_id,
test_id, create_time, `template`
)
VALUES (#{id,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR}, #{event,jdbcType=VARCHAR},
#{userId,jdbcType=VARCHAR}, #{taskType,jdbcType=VARCHAR}, #{webhook,jdbcType=VARCHAR},
#{identification,jdbcType=VARCHAR}, #{isSet,jdbcType=BIT}, #{organizationId,jdbcType=VARCHAR},
#{testId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{template,jdbcType=LONGVARCHAR}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.MessageTask">
insert into message_task
@ -152,6 +178,9 @@
<if test="createTime != null">
create_time,
</if>
<if test="template != null">
`template`,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -187,6 +216,9 @@
<if test="createTime != null">
#{createTime,jdbcType=BIGINT},
</if>
<if test="template != null">
#{template,jdbcType=LONGVARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.MessageTaskExample" resultType="java.lang.Long">
@ -231,24 +263,45 @@
<if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT},
</if>
<if test="record.template != null">
`template` = #{record.template,jdbcType=LONGVARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExampleWithBLOBs" parameterType="map">
UPDATE message_task
SET id = #{record.id,jdbcType=VARCHAR},
`type` = #{record.type,jdbcType=VARCHAR},
event = #{record.event,jdbcType=VARCHAR},
user_id = #{record.userId,jdbcType=VARCHAR},
task_type = #{record.taskType,jdbcType=VARCHAR},
webhook = #{record.webhook,jdbcType=VARCHAR},
identification = #{record.identification,jdbcType=VARCHAR},
is_set = #{record.isSet,jdbcType=BIT},
organization_id = #{record.organizationId,jdbcType=VARCHAR},
test_id = #{record.testId,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
`template` = #{record.template,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update message_task
set id = #{record.id,jdbcType=VARCHAR},
`type` = #{record.type,jdbcType=VARCHAR},
event = #{record.event,jdbcType=VARCHAR},
user_id = #{record.userId,jdbcType=VARCHAR},
task_type = #{record.taskType,jdbcType=VARCHAR},
webhook = #{record.webhook,jdbcType=VARCHAR},
identification = #{record.identification,jdbcType=VARCHAR},
is_set = #{record.isSet,jdbcType=BIT},
organization_id = #{record.organizationId,jdbcType=VARCHAR},
test_id = #{record.testId,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT}
`type` = #{record.type,jdbcType=VARCHAR},
event = #{record.event,jdbcType=VARCHAR},
user_id = #{record.userId,jdbcType=VARCHAR},
task_type = #{record.taskType,jdbcType=VARCHAR},
webhook = #{record.webhook,jdbcType=VARCHAR},
identification = #{record.identification,jdbcType=VARCHAR},
is_set = #{record.isSet,jdbcType=BIT},
organization_id = #{record.organizationId,jdbcType=VARCHAR},
test_id = #{record.testId,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -286,21 +339,39 @@
<if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT},
</if>
<if test="template != null">
`template` = #{template,jdbcType=LONGVARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.MessageTask">
UPDATE message_task
SET `type` = #{type,jdbcType=VARCHAR},
event = #{event,jdbcType=VARCHAR},
user_id = #{userId,jdbcType=VARCHAR},
task_type = #{taskType,jdbcType=VARCHAR},
webhook = #{webhook,jdbcType=VARCHAR},
identification = #{identification,jdbcType=VARCHAR},
is_set = #{isSet,jdbcType=BIT},
organization_id = #{organizationId,jdbcType=VARCHAR},
test_id = #{testId,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
`template` = #{template,jdbcType=LONGVARCHAR}
WHERE id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.MessageTask">
update message_task
set `type` = #{type,jdbcType=VARCHAR},
event = #{event,jdbcType=VARCHAR},
user_id = #{userId,jdbcType=VARCHAR},
task_type = #{taskType,jdbcType=VARCHAR},
webhook = #{webhook,jdbcType=VARCHAR},
identification = #{identification,jdbcType=VARCHAR},
is_set = #{isSet,jdbcType=BIT},
organization_id = #{organizationId,jdbcType=VARCHAR},
test_id = #{testId,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT}
where id = #{id,jdbcType=VARCHAR}
UPDATE message_task
SET `type` = #{type,jdbcType=VARCHAR},
event = #{event,jdbcType=VARCHAR},
user_id = #{userId,jdbcType=VARCHAR},
task_type = #{taskType,jdbcType=VARCHAR},
webhook = #{webhook,jdbcType=VARCHAR},
identification = #{identification,jdbcType=VARCHAR},
is_set = #{isSet,jdbcType=BIT},
organization_id = #{organizationId,jdbcType=VARCHAR},
test_id = #{testId,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT}
WHERE id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -14,6 +14,7 @@
<result column="last_organization_id" jdbcType="VARCHAR" property="lastOrganizationId" />
<result column="phone" jdbcType="VARCHAR" property="phone" />
<result column="source" jdbcType="VARCHAR" property="source" />
<result column="last_project_id" jdbcType="VARCHAR" property="lastProjectId" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -75,7 +76,7 @@
</sql>
<sql id="Base_Column_List">
id, `name`, email, `password`, `status`, create_time, update_time, `language`, last_workspace_id,
last_organization_id, phone, `source`
last_organization_id, phone, `source`, last_project_id
</sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.UserExample" resultMap="BaseResultMap">
select
@ -111,13 +112,13 @@
insert into user (id, `name`, email,
`password`, `status`, create_time,
update_time, `language`, last_workspace_id,
last_organization_id, phone, `source`
)
last_organization_id, phone, `source`,
last_project_id)
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR},
#{password,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT},
#{updateTime,jdbcType=BIGINT}, #{language,jdbcType=VARCHAR}, #{lastWorkspaceId,jdbcType=VARCHAR},
#{lastOrganizationId,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}, #{source,jdbcType=VARCHAR}
)
#{lastOrganizationId,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}, #{source,jdbcType=VARCHAR},
#{lastProjectId,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.User">
insert into user
@ -158,6 +159,9 @@
<if test="source != null">
`source`,
</if>
<if test="lastProjectId != null">
last_project_id,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -196,6 +200,9 @@
<if test="source != null">
#{source,jdbcType=VARCHAR},
</if>
<if test="lastProjectId != null">
#{lastProjectId,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.UserExample" resultType="java.lang.Long">
@ -243,6 +250,9 @@
<if test="record.source != null">
`source` = #{record.source,jdbcType=VARCHAR},
</if>
<if test="record.lastProjectId != null">
last_project_id = #{record.lastProjectId,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -261,7 +271,8 @@
last_workspace_id = #{record.lastWorkspaceId,jdbcType=VARCHAR},
last_organization_id = #{record.lastOrganizationId,jdbcType=VARCHAR},
phone = #{record.phone,jdbcType=VARCHAR},
`source` = #{record.source,jdbcType=VARCHAR}
`source` = #{record.source,jdbcType=VARCHAR},
last_project_id = #{record.lastProjectId,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -302,6 +313,9 @@
<if test="source != null">
`source` = #{source,jdbcType=VARCHAR},
</if>
<if test="lastProjectId != null">
last_project_id = #{lastProjectId,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -317,7 +331,8 @@
last_workspace_id = #{lastWorkspaceId,jdbcType=VARCHAR},
last_organization_id = #{lastOrganizationId,jdbcType=VARCHAR},
phone = #{phone,jdbcType=VARCHAR},
`source` = #{source,jdbcType=VARCHAR}
`source` = #{source,jdbcType=VARCHAR},
last_project_id = #{lastProjectId,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -14,4 +14,6 @@ public interface ExtApiScenarioMapper {
List<ApiScenario> selectIds(@Param("ids") List<String> ids);
List<ApiScenario> selectReference(@Param("request") ApiScenarioRequest request);
}

View File

@ -12,6 +12,7 @@
select api_scenario.id, api_scenario.project_id, api_scenario.tag_id, api_scenario.user_id,
api_scenario.api_scenario_module_id,api_scenario.module_path, api_scenario.name, api_scenario.level,
api_scenario.status, api_scenario.principal, api_scenario.step_total, api_scenario.follow_people,
api_scenario.last_result,api_scenario.pass_rate,api_scenario.report_id,
api_scenario.schedule, api_scenario.description, api_scenario.create_time, api_scenario.update_time,
project.name as project_name, user.name as user_name
from api_scenario
@ -70,4 +71,23 @@
#{v}
</foreach>
</select>
<select id="selectReference" resultType="io.metersphere.base.domain.ApiScenario">
select * from api_scenario
<where>
id != #{request.id}
<if test="request.workspaceId != null">
AND project.workspace_id = #{request.workspaceId}
</if>
<if test="request.projectId != null">
AND project_id = #{request.projectId}
</if>
<if test="request.moduleId != null">
AND api_scenario_module_id = #{request.moduleId}
</if>
and scenario_definition like CONCAT('%', #{request.id},'%')
</where>
</select>
</mapper>

View File

@ -17,4 +17,5 @@ public class MessageDetail {
private Boolean isSet;
private String testId;
private Long createTime;
private String template;
}

View File

@ -5,7 +5,9 @@ import io.metersphere.commons.utils.LogUtil;
import io.metersphere.notice.domain.MessageDetail;
import io.metersphere.notice.domain.UserDetail;
import io.metersphere.service.UserService;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Resource;
@ -14,6 +16,7 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public abstract class AbstractNoticeSender implements NoticeSender {
@ -21,13 +24,16 @@ public abstract class AbstractNoticeSender implements NoticeSender {
private UserService userService;
protected String getContext(MessageDetail messageDetail, NoticeModel noticeModel) {
// 如果配置了模版就直接使用模版
if (StringUtils.isNotBlank(messageDetail.getTemplate())) {
return getContent(messageDetail.getTemplate(), noticeModel.getParamMap());
}
// 处理 userIds 中包含的特殊值
List<String> realUserIds = getRealUserIds(messageDetail.getUserIds(), noticeModel.getRelatedUsers(), messageDetail.getEvent());
messageDetail.setUserIds(realUserIds);
// 处理 WeCom Ding context
String context = "";
String status = noticeModel.getStatus();
switch (messageDetail.getEvent()) {
case NoticeConstants.Event.CREATE:
case NoticeConstants.Event.UPDATE:
@ -48,6 +54,10 @@ public abstract class AbstractNoticeSender implements NoticeSender {
}
protected String getHtmlContext(MessageDetail messageDetail, NoticeModel noticeModel) {
// 如果配置了模版就直接使用模版
if (StringUtils.isNotBlank(messageDetail.getTemplate())) {
return getContent(messageDetail.getTemplate(), noticeModel.getParamMap());
}
// 处理 userIds 中包含的特殊值
List<String> realUserIds = getRealUserIds(messageDetail.getUserIds(), noticeModel.getRelatedUsers(), messageDetail.getEvent());
messageDetail.setUserIds(realUserIds);
@ -77,7 +87,20 @@ public abstract class AbstractNoticeSender implements NoticeSender {
} catch (IOException e) {
LogUtil.error(e);
}
return context;
return getContent(context, noticeModel.getParamMap());
}
protected String getContent(String template, Map<String, Object> context) {
if (MapUtils.isNotEmpty(context)) {
for (String k : context.keySet()) {
if (context.get(k) != null) {
template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", context.get(k).toString());
} else {
template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", "未设置");
}
}
}
return template;
}
protected List<String> getUserPhones(List<String> userIds) {

View File

@ -5,8 +5,6 @@ import io.metersphere.notice.domain.MessageDetail;
import io.metersphere.notice.sender.AbstractNoticeSender;
import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.MailService;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
@ -15,14 +13,13 @@ import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.List;
import java.util.Map;
@Component
public class MailNoticeSender extends AbstractNoticeSender {
@Resource
private MailService mailService;
private void sendMail(MessageDetail messageDetail, String template, NoticeModel noticeModel) throws MessagingException {
private void sendMail(MessageDetail messageDetail, String context, NoticeModel noticeModel) throws MessagingException {
JavaMailSenderImpl javaMailSender = mailService.getMailSender();
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
@ -31,25 +28,11 @@ public class MailNoticeSender extends AbstractNoticeSender {
List<String> emails = super.getUserEmails(messageDetail.getUserIds());
String[] users = emails.toArray(new String[0]);
LogUtil.info("收件人地址: " + emails);
helper.setText(this.getContent(template, noticeModel.getParamMap()), true);
helper.setText(context, true);
helper.setTo(users);
javaMailSender.send(mimeMessage);
}
public String getContent(String template, Map<String, Object> context) {
if (MapUtils.isNotEmpty(context)) {
for (String k : context.keySet()) {
if (context.get(k) != null) {
template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", context.get(k).toString());
} else {
template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", "未设置");
}
}
}
return template;
}
@Override
public void send(MessageDetail messageDetail, NoticeModel noticeModel) {
String context = super.getHtmlContext(messageDetail, noticeModel);

View File

@ -2,7 +2,6 @@ package io.metersphere.notice.service;
import com.alibaba.nacos.client.utils.StringUtils;
import io.metersphere.commons.constants.NoticeConstants;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.notice.domain.MessageDetail;
import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.sender.NoticeSender;
@ -22,10 +21,8 @@ public class NoticeSendService {
private WeComNoticeSender weComNoticeSender;
@Resource
private DingNoticeSender dingNoticeSender;
private void event(String event) {
}
@Resource
private NoticeService noticeService;
private NoticeSender getNoticeSender(MessageDetail messageDetail) {
NoticeSender noticeSender = null;
@ -47,8 +44,6 @@ public class NoticeSendService {
}
public void send(String taskType, NoticeModel noticeModel) {
NoticeService noticeService = CommonBeanFactory.getBean(NoticeService.class);
assert noticeService != null;
List<MessageDetail> messageDetails;
switch (taskType) {
case NoticeConstants.Mode.API:

View File

@ -33,14 +33,17 @@ public class NoticeService {
SessionUser user = SessionUtils.getUser();
String orgId = user.getLastOrganizationId();
long time = System.currentTimeMillis();
String identification = UUID.randomUUID().toString();
messageDetail.getUserIds().forEach(m -> {
checkUserIdExist(m, messageDetail, orgId);
String identification = messageDetail.getIdentification();
if (StringUtils.isBlank(identification)) {
identification = UUID.randomUUID().toString();
}
for (String userId : messageDetail.getUserIds()) {
checkUserIdExist(userId, messageDetail, orgId);
MessageTask messageTask = new MessageTask();
messageTask.setId(UUID.randomUUID().toString());
messageTask.setEvent(messageDetail.getEvent());
messageTask.setTaskType(messageDetail.getTaskType());
messageTask.setUserId(m);
messageTask.setUserId(userId);
messageTask.setType(messageDetail.getType());
messageTask.setWebhook(messageDetail.getWebhook());
messageTask.setIdentification(identification);
@ -48,8 +51,15 @@ public class NoticeService {
messageTask.setOrganizationId(orgId);
messageTask.setTestId(messageDetail.getTestId());
messageTask.setCreateTime(time);
setTemplate(messageDetail, messageTask);
messageTaskMapper.insert(messageTask);
});
}
}
private void setTemplate(MessageDetail messageDetail, MessageTask messageTask) {
if (StringUtils.isNotBlank(messageDetail.getTemplate())) {
messageTask.setTemplate(messageDetail.getTemplate());
}
}
private void checkUserIdExist(String userId, MessageDetail list, String orgId) {
@ -80,7 +90,7 @@ public class NoticeService {
public List<MessageDetail> searchMessageByTestId(String testId) {
MessageTaskExample example = new MessageTaskExample();
example.createCriteria().andTestIdEqualTo(testId);
List<MessageTask> messageTaskLists = messageTaskMapper.selectByExample(example);
List<MessageTask> messageTaskLists = messageTaskMapper.selectByExampleWithBLOBs(example);
List<MessageDetail> scheduleMessageTask = new ArrayList<>();
Map<String, List<MessageTask>> MessageTaskMap = messageTaskLists.stream().collect(Collectors.groupingBy(MessageTask::getIdentification));
MessageTaskMap.forEach((k, v) -> {
@ -100,7 +110,7 @@ public class NoticeService {
example.createCriteria()
.andTaskTypeEqualTo(type)
.andOrganizationIdEqualTo(orgId);
List<MessageTask> messageTaskLists = messageTaskMapper.selectByExample(example);
List<MessageTask> messageTaskLists = messageTaskMapper.selectByExampleWithBLOBs(example);
Map<String, List<MessageTask>> messageTaskMap = messageTaskLists.stream()
.collect(Collectors.groupingBy(NoticeService::fetchGroupKey));
@ -130,6 +140,7 @@ public class NoticeService {
messageDetail.setType(m.getType());
messageDetail.setIsSet(m.getIsSet());
messageDetail.setCreateTime(m.getCreateTime());
messageDetail.setTemplate(m.getTemplate());
}
if (CollectionUtils.isNotEmpty(userIds)) {
messageDetail.setUserIds(new ArrayList<>(userIds));

View File

@ -15,6 +15,8 @@ import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -49,7 +51,7 @@ public class PerformanceNoticeTask {
}
try {
//查询定时任务是否关闭
Thread.sleep(1000 * 30);// 每分钟检查 loadtest 的状态
Thread.sleep(1000 * 10);// 检查 loadtest 的状态
} catch (InterruptedException e) {
LogUtil.error(e.getMessage(), e);
}
@ -81,7 +83,11 @@ public class PerformanceNoticeTask {
if (PerformanceTestStatus.Error.name().equals(loadTestReport.getStatus())) {
event = NoticeConstants.Event.EXECUTE_FAILED;
}
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("testName", loadTestReport.getName());
paramMap.put("id", loadTestReport.getId());
paramMap.put("type", "performance");
paramMap.put("url", baseSystemConfigDTO.getUrl());
NoticeModel noticeModel = NoticeModel.builder()
.successContext(successContext)
.successMailTemplate("PerformanceApiSuccessNotification")
@ -91,6 +97,7 @@ public class PerformanceNoticeTask {
.status(loadTestReport.getStatus())
.subject(subject)
.event(event)
.paramMap(paramMap)
.build();
noticeSendService.send(loadTestReport.getTriggerMode(), noticeModel);
}

View File

@ -0,0 +1,13 @@
ALTER TABLE message_task
MODIFY identification varchar(50) NOT NULL;
ALTER TABLE message_task
MODIFY organization_id varchar(50) NULL;
ALTER TABLE message_task
MODIFY test_id varchar(50) NULL;
ALTER TABLE message_task
ADD template TEXT NULL;
DROP TABLE IF EXISTS notice;

View File

@ -0,0 +1 @@
alter table user add last_project_id varchar(50) null;

View File

@ -64,8 +64,8 @@
<!--要生成的数据库表 -->
<table tableName="api_scenario"/>
<table tableName="api_scenario_report"/>
<table tableName="api_scenario_report_detail"/>
</context>
</generatorConfiguration>

View File

@ -4,7 +4,7 @@
<el-card>
<section class="report-container" v-if="this.report.testId">
<ms-api-report-view-header :report="report" @reportExport="handleExport" @reportSave="handleSave"/>
<ms-api-report-view-header :debug="debug" :report="report" @reportExport="handleExport" @reportSave="handleSave"/>
<main v-if="this.isNotRunning">
<ms-metric-chart :content="content" :totalTime="totalTime"/>
@ -71,6 +71,7 @@
reportId: String,
currentProjectId: String,
infoDb: Boolean,
debug: Boolean,
},
watch: {
reportId() {
@ -178,6 +179,8 @@
this.$success(this.$t('commons.save_success'));
this.loading = false;
this.$emit('refresh');
}, error => {
this.loading = false;
});
},
exportReportReset() {

View File

@ -2,14 +2,14 @@
<header class="report-header">
<el-row>
<el-col>
<span><el-input size="mini" style="width: 200px" v-model="report.name"/> </span>
<span v-if="!debug"><el-input size="mini" style="width: 200px" v-model="report.name"/> </span>
<span class="time"> {{ report.createTime | timestampFormatDate }}</span>
<el-button :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)" style="margin-right: 10px">
{{$t('test_track.plan_view.export_report')}}
</el-button>
<el-button :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleSave(report.name)" style="margin-right: 10px">
<el-button v-if="!debug" :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleSave(report.name)" style="margin-right: 10px">
{{$t('commons.save')}}
</el-button>
@ -23,7 +23,10 @@
export default {
name: "MsApiReportViewHeader",
props: ['report'],
props: {
report: {},
debug: Boolean,
},
computed: {
path() {
return "/api/test/edit?id=" + this.report.testId;

View File

@ -1,34 +1,18 @@
<template>
<div class="metric-container">
<el-row type="flex">
<div class="metric">
<div class="value">{{request.responseResult.responseTime}} ms</div>
<div class="name">{{$t('api_report.response_time')}}</div>
<br>
<div class="value">{{request.responseResult.latency}} ms</div>
<div class="name">{{$t('api_report.latency')}}</div>
</div>
<div class="metric">
<div class="value">{{request.requestSize}} bytes</div>
<div class="name">{{$t('api_report.request_size')}}</div>
<br>
<div class="value">{{request.responseResult.responseSize}} bytes</div>
<div class="name">{{$t('api_report.response_size')}}</div>
</div>
<div class="metric horizontal">
<el-row type="flex">
<div class="code">
<div class="value" :class="{'error': error}">{{request.responseResult.responseCode}}</div>
<div class="name">{{$t('api_report.response_code')}}</div>
</div>
<div class="split"></div>
<div class="message">
<div class="value">{{request.responseResult.responseMessage}}</div>
<div class="name">{{$t('api_report.response_message')}}</div>
</div>
</el-row>
</div>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_code')}} :</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{request.responseResult.responseCode ? request.responseResult.responseCode :'0'}}</div>
</el-col>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_time')}} :</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{request.responseResult.responseTime?request.responseResult.responseTime:0}} ms</div>
</el-col>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_size')}} :</div>
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">{{request.responseResult.responseSize?request.responseResult.responseSize:0}} bytes</div>
</el-col>
</el-row>
</div>
</template>

View File

@ -3,7 +3,7 @@
<p class="el-divider--horizontal"></p>
<div @click="active">
<el-row :gutter="10" type="flex" align="middle" class="info">
<el-col :span="10" v-if="indexNumber!=undefined">
<el-col :span="14" v-if="indexNumber!=undefined">
<div class="method">
<div class="el-step__icon is-text ms-api-col" v-if="indexNumber%2 ==0">
<div class="el-step__icon-inner"> {{ indexNumber+1 }}</div>
@ -14,14 +14,10 @@
{{ request.name }}
</div>
</el-col>
<el-col :span="4">
<div class="method">
{{ request.method }}
</div>
</el-col>
<el-col :span="5">
<el-tooltip effect="dark" :content="request.responseResult.responseCode" placement="bottom" :open-delay="800">
<div class="url">{{ request.responseResult.responseCode }}</div>
<div class="url" style="color: #5daf34">{{ request.responseResult.responseCode }}</div>
</el-tooltip>
</el-col>
<el-col :span="3">
@ -122,16 +118,18 @@
}
.ms-api-col {
background-color: #FCF1F1;
border-color: #67C23A;
background-color: #EFF0F0;
border-color: #EFF0F0;
margin-right: 10px;
color: #67C23A;
font-size: 12px;
color: #64666A;
}
.ms-api-col-create {
background-color: #EBF2F2;
border-color: #008080;
margin-right: 10px;
font-size: 12px;
color: #008080;
}

View File

@ -1,27 +1,6 @@
<template>
<div class="request-result">
<div @click="active">
<el-row :gutter="10" type="flex" align="middle" class="info">
<el-col :span="12">
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
{{scenarioName}}
</el-col>
<el-col :span="4">
{{$t('api_report.start_time')}}
</el-col>
<el-col :span="2">
{{$t('api_report.response_time')}}
</el-col>
<el-col :span="2">
{{$t('api_report.error')}}
</el-col>
<el-col :span="2">
{{$t('api_report.assertions')}}
</el-col>
<el-col :span="2">
{{$t('api_report.result')}}
</el-col>
</el-row>
<div>
<el-row :gutter="10" type="flex" align="middle" class="info">
<el-col :span="2">
<div class="method">
@ -35,7 +14,7 @@
</el-tooltip>
</el-col>
<el-col :span="4">
{{request.startTime | timestampFormatDate(true) }}
{{request.startTime | timestampFormatDate(true) }}
</el-col>
<el-col :span="2">
<div class="time">
@ -67,7 +46,7 @@
</el-tab-pane>
<el-tab-pane :label="$t('api_report.request_result')" name="result">
<ms-request-metric :request="request"/>
<ms-request-text :request="request"/>
<ms-request-text :request="request"/>
<br>
<ms-response-text :request-type="requestType" :response="request.responseResult"/>
</el-tab-pane>

View File

@ -21,6 +21,13 @@
<!-- 请求参数-->
<el-collapse-transition>
<div v-if="request.active">
<div v-if="request.protocol === 'HTTP'">
<el-input :placeholder="$t('api_test.definition.request.path_all_info')" v-model="request.url" style="width: 85%;margin-top: 10px" size="small">
<el-select v-model="request.method" slot="prepend" style="width: 100px" size="small">
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</el-input>
</div>
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<ms-api-request-form :headers="request.headers " :request="request" v-if="request.protocol==='HTTP'"/>
<ms-tcp-basis-parameters :request="request" :currentProject="currentProject" v-if="request.protocol==='TCP'"/>
@ -41,6 +48,7 @@
import MsTcpBasisParameters from "../../definition/components/request/tcp/BasisParameters";
import MsDubboBasisParameters from "../../definition/components/request/dubbo/BasisParameters";
import MsApiRequestForm from "../../definition/components/request/http/ApiRequestForm";
import {REQ_METHOD} from "../../definition/model/JsonData";
export default {
name: "MsApiComponent",
@ -51,7 +59,22 @@
},
components: {MsSqlBasisParameters, MsTcpBasisParameters, MsDubboBasisParameters, MsApiRequestForm},
data() {
return {loading: false,}
return {loading: false, reqOptions: REQ_METHOD,}
},
created() {
if (this.request.protocol === 'HTTP') {
try {
let urlObject = new URL(this.request.url);
let url = urlObject.protocol + "//" + urlObject.host + "/";
let path = this.request.url.substr(url.length);
if (!path.startsWith('/')) {
path = "/" + path;
}
this.request.path = path;
} catch (e) {
this.request.path = this.request.url;
}
}
},
methods: {
remove() {
@ -94,4 +117,12 @@
transform: rotate(90deg);
}
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
margin: 20px 0;
}
</style>

View File

@ -58,6 +58,10 @@
let name = this.request.name;
Object.assign(this.request, row.request);
this.request.name = name;
if (this.request.protocol === 'HTTP') {
this.request.url = row.url;
this.request.method = row.method;
}
this.request.resourceId = getUUID();
this.$emit('addCustomizeApi', this.request);
},
@ -72,5 +76,12 @@
</script>
<style scoped>
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
margin: 20px 0;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div>
<el-card class="table-card" v-loading="result.loading">
<el-card class="table-card" v-loading="loading">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" title=""
:show-create="false"/>
@ -37,31 +37,30 @@
</template>
</el-table-column>
<el-table-column prop="stepTotal" :label="$t('api_test.automation.step')" show-overflow-tooltip/>
<el-table-column prop="status" :label="$t('api_test.automation.last_result')">
<el-table-column prop="lastResult" :label="$t('api_test.automation.last_result')">
<template v-slot:default="{row}">
<el-link type="success" v-if="row.status === 'Success'">{{ $t('api_test.automation.success') }}</el-link>
<el-link type="danger" v-if="row.status === 'Fail'">{{ $t('api_test.automation.fail') }}</el-link>
<el-link type="warning" v-if="row.status === 'Trash'">{{ $t('api_test.automation.trash') }}</el-link>
<el-link type="success" @click="showReport(row)" v-if="row.lastResult === 'Success'">{{ $t('api_test.automation.success') }}</el-link>
<el-link type="danger" @click="showReport(row)" v-if="row.lastResult === 'Fail'">{{ $t('api_test.automation.fail') }}</el-link>
</template>
</el-table-column>
<el-table-column prop="passingRate" :label="$t('api_test.automation.passing_rate')"
<el-table-column prop="passRate" :label="$t('api_test.automation.passing_rate')"
show-overflow-tooltip/>
<el-table-column :label="$t('commons.operating')" width="180">
<el-table-column :label="$t('commons.operating')" width="200px" v-if="!referenced">
<template v-slot:default="{row}">
<el-button type="text" @click="edit(row)">{{ $t('api_test.automation.edit') }}</el-button>
<el-button type="text" @click="execute(row)">{{ $t('api_test.automation.execute') }}</el-button>
<el-button type="text" @click="copy(row)">{{ $t('api_test.automation.copy') }}</el-button>
<el-button type="text" @click="remove(row)">{{ $t('api_test.automation.remove') }}</el-button>
<ms-scenario-extend-buttons :row="row"/>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
<div>
<!-- 调试结果 -->
<!-- 执行结果 -->
<el-drawer :visible.sync="runVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('test_track.plan_view.test_result')" :modal="false" size="90%">
<ms-api-report-detail :report-id="reportId" :currentProjectId="currentProject!=undefined ? currentProject.id:''"/>
<ms-api-report-detail @refresh="search" :infoDb="infoDb" :report-id="reportId" :currentProjectId="currentProject!=undefined ? currentProject.id:''"/>
</el-drawer>
</div>
</el-card>
@ -76,18 +75,27 @@
import MsTag from "../../../common/components/MsTag";
import {getUUID} from "@/common/js/utils";
import MsApiReportDetail from "../report/ApiReportDetail";
import MsTableMoreBtn from "./TableMoreBtn";
import MsScenarioExtendButtons from "@/business/components/api/automation/scenario/ScenarioExtendBtns";
export default {
name: "MsApiScenarioList",
components: {ShowMoreBtn, MsTablePagination, MsTableHeader, MsTag, MsApiReportDetail},
components: {MsTablePagination, MsTableMoreBtn, ShowMoreBtn, MsTableHeader, MsTag, MsApiReportDetail, MsScenarioExtendButtons},
props: {
currentProject: Object,
currentModule: Object,
referenced: {
type: Boolean,
default: false,
}
},
data() {
return {
result: {},
loading: false,
condition: {},
currentScenario: {},
schedule: {},
selectAll: false,
selection: [],
tableData: [],
@ -95,6 +103,7 @@
pageSize: 10,
total: 0,
reportId: "",
infoDb: false,
runVisible: false,
runData: [],
buttons: [
@ -116,6 +125,7 @@
},
methods: {
search() {
this.loading = true;
this.condition.filters = ["Prepare", "Underway", "Completed"];
if (this.currentModule != null) {
if (this.currentModule.id === "root") {
@ -132,10 +142,11 @@
}
let url = "/api/automation/list/" + this.currentPage + "/" + this.pageSize;
this.result = this.$post(url, this.condition, response => {
this.$post(url, this.condition, response => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.loading = false;
});
},
handleCommand(cmd) {
@ -154,12 +165,13 @@
},
handleBatchExecute() {
this.infoDb = false;
let url = "/api/automation/run";
let run = {};
let scenarioIds = this.selection;
run.id = getUUID();
run.scenarioIds = scenarioIds;
this.result = this.$post(url, run, response => {
this.$post(url, run, response => {
let data = response.data;
this.runVisible = true;
this.reportId = run.id;
@ -179,13 +191,14 @@
this.$emit('edit', row);
},
execute(row) {
this.infoDb = false;
let url = "/api/automation/run";
let run = {};
let scenarioIds = [];
scenarioIds.push(row.id);
run.id = getUUID();
run.scenarioIds = scenarioIds;
this.result = this.$post(url, run, response => {
this.$post(url, run, response => {
let data = response.data;
this.runVisible = true;
this.reportId = run.id;
@ -195,6 +208,47 @@
row.id = getUUID();
this.$emit('edit', row);
},
showReport(row) {
this.runVisible = true;
this.infoDb = true;
this.reportId = row.reportId;
},
handleQuote() {
},
handleSchedule(row) {
this.currentScenario = row;
if (row.schedule) {
if (Object.prototype.toString.call(row.schedule).match(/\[object (\w+)\]/)[1].toLowerCase() === 'object') {
this.schedule = row.schedule;
} else {
this.schedule = JSON.parse(row.schedule);
}
}
this.$refs.scheduleEdit.open();
},
saveCronExpression(cronExpression) {
this.schedule.enable = true;
this.schedule.value = cronExpression;
this.saveSchedule();
},
saveSchedule() {
this.checkScheduleEdit();
let param = {};
param = this.schedule;
param.resourceId = this.currentScenario.id;
let url = '/api/automation/schedule/create';
if (param.id) {
url = '/api/automation/schedule/update';
}
this.$post(url, param, () => {
this.$success(this.$t('commons.save_success'));
this.search();
});
},
checkScheduleEdit() {
return true;
},
remove(row) {
if (this.currentModule !== undefined && this.currentModule != null && this.currentModule.id === "gc") {
this.$get('/api/automation/delete/' + row.id, () => {

View File

@ -201,9 +201,9 @@
<div v-if="operatingElements.indexOf('HTTPSamplerProxy')>0 || operatingElements.indexOf('DubboSampler')>0 || operatingElements.indexOf('JDBCSampler')>0 || operatingElements.indexOf('TCPSampler')>0 ">
<el-button class="ms-right-buttion" size="small" style="color: #F56C6C;background-color: #FCF1F1" @click="apiListImport">+{{$t('api_test.automation.api_list_import')}}</el-button>
</div>
<div v-if="operatingElements.indexOf('OT_IMPORT')>0">
<!--<div v-if="operatingElements.indexOf('OT_IMPORT')>0">
<el-button class="ms-right-buttion" size="small" style="color: #409EFF;background-color: #EEF5FE" @click="addComponent('OT_IMPORT')">+{{$t('api_test.automation.external_import')}}</el-button>
</div>
</div>-->
<div v-if="operatingElements.indexOf('ConstantTimer')>0">
<el-button class="ms-right-buttion" size="small" style="color: #67C23A;background-color: #F2F9EE" @click="addComponent('ConstantTimer')">+{{$t('api_test.automation.wait_controller')}}</el-button>
</div>
@ -263,7 +263,7 @@
@runRefresh="runRefresh" ref="runTest"/>
<!-- 调试结果 -->
<el-drawer :visible.sync="debugVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('test_track.plan_view.test_result')" :modal="false" size="90%">
<ms-api-report-detail :report-id="reportId" :currentProjectId="currentProject.id"/>
<ms-api-report-detail :report-id="reportId" :debug="true" :currentProjectId="currentProject.id"/>
</el-drawer>
<!--场景公共参数-->
@ -342,7 +342,7 @@
debugVisible: false,
customizeRequest: {protocol: "HTTP", type: "API", hashTree: [], referenced: 'Created', active: false},
operatingElements: [],
currentRow: {cases: [], apis: []},
currentRow: {cases: [], apis: [], referenced: true},
selectedTreeNode: undefined,
expandedNode: [],
scenarioDefinition: [],
@ -553,7 +553,11 @@
this.$error(this.$t('api_test.environment.select_environment'));
return;
}
this.debugData = {id: this.currentScenario.id, name: this.currentScenario.name, type: "scenario", referenced: 'Created', environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition};
this.debugData = {
id: this.currentScenario.id, name: this.currentScenario.name, type: "scenario",
variables: this.currentScenario.variables, referenced: 'Created',
environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition
};
this.reportId = getUUID().substring(0, 8);
},
getEnvironments() {
@ -681,6 +685,7 @@
if (response.data.scenarioDefinition != null) {
let obj = JSON.parse(response.data.scenarioDefinition);
this.currentEnvironmentId = obj.environmentId;
this.currentScenario.variables = obj.variables;
this.scenarioDefinition = obj.hashTree;
}
}
@ -695,7 +700,10 @@
this.currentScenario.stepTotal = this.scenarioDefinition.length;
this.currentScenario.modulePath = this.getPath(this.currentScenario.apiScenarioModuleId);
// 便
let scenario = {id: this.currentScenario.id, name: this.currentScenario.name, type: "scenario", referenced: 'Created', environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition};
let scenario = {
id: this.currentScenario.id, name: this.currentScenario.name, variables: this.currentScenario.variables,
type: "scenario", referenced: 'Created', environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition
};
this.currentScenario.scenarioDefinition = scenario;
this.currentScenario.tagId = JSON.stringify(this.currentScenario.tagId);
if (this.currentModule != null) {

View File

@ -10,6 +10,7 @@
:current-module="currentModule"
@edit="editScenario"
@selection="setData"
:referenced="true"
ref="apiScenarioList"/>
<el-button style="float: right;margin: 10px" @click="importApiScenario" type="primary">{{ $t('api_test.scenario.reference') }}</el-button>

View File

@ -2,10 +2,13 @@
<el-card>
<el-row>
<div>
<div class="el-step__icon is-text" :style="styleType" style="margin-right: 10px">
<div class="el-step__icon-inner">{{jsr223ProcessorData.index}}</div>
</div>
<el-button class="ms-left-buttion" size="small" :style="styleType" style="color: #B8741A;background-color: #F9F1EA">{{title}}</el-button>
<i class="icon el-icon-arrow-right" :class="{'is-active': this.jsr223ProcessorData.active}" @click="changeActive" style="margin-left: 20px"/>
<el-input size="small" v-model="jsr223ProcessorData.name" class="ms-api-header-select" style="width: 380px"/>
<el-button size="small" style="float: right" @click="remove">移除</el-button>
<el-button size="small" style="float: right" @click="remove">{{$t('commons.remove')}}</el-button>
</div>
</el-row>
<el-collapse-transition>

View File

@ -0,0 +1,62 @@
<template>
<el-dialog :close-on-click-modal="false" :title="$t('api_test.automation.case_ref')" :visible.sync="visible"
width="45%" :destroy-on-close="true">
<span>{{ $t('api_test.automation.scenario_ref') }}</span>
<div class="refs" v-loading="scenarioLoading">
<div v-for="(item, index) in scenarioRefs" :key="index" class="el-button--text">{{ item.name }}</div>
</div>
<span>{{ $t('api_test.automation.plan_ref') }}</span>
<div class="refs">
<div v-for="(item, index) in planRefs" :key="index" class="el-button--text">{{ item.name }}</div>
</div>
<template v-slot:footer>
<div class="dialog-footer">
<el-button type="primary" @click="visible = false" @keydown.enter.native.prevent>
{{ $t('commons.confirm') }}
</el-button>
</div>
</template>
</el-dialog>
</template>
<script>
export default {
name: "MsReferenceView",
components: {},
data() {
return {
visible: false,
scenarioLoading: false,
scenarioRefs: [],
planRefs: []
}
},
methods: {
getReferenceData(row) {
this.scenarioLoading = true;
this.scenarioRefs = [];
this.$post("/api/automation/getReference/", row, response => {
this.scenarioRefs = response.data;
this.scenarioLoading = false;
})
},
open(row) {
this.getReferenceData(row);
this.visible = true
}
}
}
</script>
<style scoped>
.refs {
min-height: 50px;
max-height: 200px;
overflow-y: auto;
font-size: 12px;
padding-bottom: 10px;
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<el-dropdown @command="handleCommand" class="scenario-ext-btn">
<el-link type="primary" :underline="false">
<el-icon class="el-icon-more"></el-icon>
</el-link>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="ref">{{ $t('api_test.automation.view_ref') }}</el-dropdown-item>
</el-dropdown-menu>
<ms-reference-view ref="viewRef"/>
</el-dropdown>
</template>
<script>
import MsReferenceView from "@/business/components/api/automation/scenario/ReferenceView";
export default {
name: "MsScenarioExtendButtons",
components: {MsReferenceView},
props: {
row: Object
},
methods: {
handleCommand(cmd) {
switch (cmd) {
case "ref":
this.$refs.viewRef.open(this.row);
break;
}
},
}
}
</script>
<style scoped>
.scenario-ext-btn {
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<div v-if="isShow" style="float: right">
<el-dropdown placement="bottom" trigger="click" size="medium">
<div @click.stop class="show-more-btn">
<i class="el-icon-more ms-icon-more"/>
</div>
<el-dropdown-menu slot="dropdown" class="dropdown-menu-class">
<el-dropdown-item v-for="(btn,index) in buttons" :key="index" @click.native.stop="click(btn)">
{{btn.name}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
name: "ShowMoreBtn",
props: {
isShow: {
type: Boolean,
default: false
},
buttons: Array,
row: Object,
size: Number
},
methods: {
click(btn) {
if (btn.handleClick instanceof Function) {
btn.handleClick(this.row);
}
}
}
}
</script>
<style scoped>
.ms-icon-more {
margin-top: 15px;
transform: rotate(0deg);
}
.show-more-btn {
width: 20px;
height: 25px;
line-height: 25px;
}
.show-more-btn-title {
color: #696969;
background-color: #e2e2e2;
padding: 5px;
}
.dropdown-menu-class {
padding: 1px 0;
text-align: center;
}
</style>

View File

@ -193,7 +193,7 @@
},
editApi(row) {
this.currentApi = row;
this.handleTabsEdit(row.name, "add");
this.handleTabsEdit(this.$t('api_test.definition.request.edit_api') + "-" + row.name, "add");
},
handleCase(testCase) {
this.currentApi = testCase;
@ -219,7 +219,7 @@
for (let index in this.apiTabs) {
let tab = this.apiTabs[index];
if (tab.name === this.apiDefaultTab) {
tab.title = data.name;
tab.title = this.$t('api_test.definition.request.edit_api') + "-" + data.name;
break;
}
}

View File

@ -358,7 +358,7 @@
let bodyUploadFiles = [];
row.bodyUploadIds = [];
let request = row.request;
if (request.body) {
if (request.body && request.body.kvs) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
@ -372,19 +372,21 @@
});
}
});
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
row.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
if (request.body.binary) {
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
row.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
}
}
return bodyUploadFiles;
},
@ -400,6 +402,9 @@
test.request = JSON.parse(test.request);
}
this.apiCaseList = response.data;
if (this.apiCaseList.length == 0) {
this.addCase();
}
});
},
validate(row) {

View File

@ -73,9 +73,14 @@
<el-table-column :label="$t('commons.operating')" min-width="130" align="center">
<template v-slot:default="scope">
<el-button type="text" @click="editApi(scope.row)">编辑</el-button>
<el-button type="text" @click="handleTestCase(scope.row)">用例</el-button>
<el-button type="text" @click="handleDelete(scope.row)" style="color: #F56C6C">删除</el-button>
<div v-if="currentRow!=undefined && currentRow.referenced">
<el-button type="text" @click="handleTestCase(scope.row)">用例</el-button>
</div>
<div v-else>
<el-button type="text" @click="editApi(scope.row)">编辑</el-button>
<el-button type="text" @click="handleTestCase(scope.row)">用例</el-button>
<el-button type="text" @click="handleDelete(scope.row)" style="color: #F56C6C">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>

View File

@ -58,25 +58,10 @@
getBodyUploadFiles(obj) {
let bodyUploadFiles = [];
obj.bodyUploadIds = [];
this.runData.forEach(request => {
if (request.body) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
if (!item.id) {
let fileId = getUUID().substring(0, 12);
item.name = item.file.name;
item.id = fileId;
}
obj.bodyUploadIds.push(item.id);
bodyUploadFiles.push(item.file);
}
});
}
});
if (request.body.binary) {
request.body.binary.forEach(param => {
if (this.runData) {
this.runData.forEach(request => {
if (request.body) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
@ -91,9 +76,26 @@
});
}
});
if (request.body.binary) {
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
if (!item.id) {
let fileId = getUUID().substring(0, 12);
item.name = item.file.name;
item.id = fileId;
}
obj.bodyUploadIds.push(item.id);
bodyUploadFiles.push(item.file);
}
});
}
});
}
}
}
});
});
}
return bodyUploadFiles;
},
run() {

View File

@ -2,8 +2,11 @@
<div :style="customizeStyle" v-loading="loading">
<el-card>
<div>
<div class="el-step__icon is-text" style="color: #A30014;background-color: #F7E6E9;margin-right: 10px" v-if="assertions.index">
<div class="el-step__icon-inner">{{assertions.index}}</div>
</div>
<el-button class="ms-left-buttion" size="small" style="color: #A30014;background-color: #F7E6E9">{{$t('api_test.definition.request.assertions_rule')}}</el-button>
<el-button size="small" style="float: right;margin-top: 0px" @click="remove">移除</el-button>
<el-button size="small" style="float: right;margin-top: 0px" @click="remove">{{$t('commons.remove')}}</el-button>
</div>
<div class="assertion-add">
<el-row :gutter="10">
@ -11,9 +14,6 @@
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type"
:placeholder="$t('api_test.request.assertions.select_type')"
size="small">
<!--
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
-->
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
<el-option :label="'JSONPath'" :value="options.JSON_PATH"/>
<el-option :label="'XPath'" :value="options.XPATH2"/>
@ -22,10 +22,6 @@
</el-select>
</el-col>
<el-col :span="20">
<!--
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT"
:callback="after"/>
-->
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"
:callback="after"/>
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
@ -41,24 +37,8 @@
</el-button>
</el-col>
</el-row>
<!--<div v-if="!scenario">-->
<!--<el-row :gutter="10" class="json-path-suggest-button">-->
<!--<el-link size="small" type="primary" @click="suggestJsonOpen" style="margin-right: 20px">-->
<!--{{ $t('api_test.request.assertions.json_path_suggest') }}-->
<!--</el-link>-->
<!--<el-link size="small" type="danger" @click="clearJson">-->
<!--{{ $t('api_test.request.assertions.json_path_clear') }}-->
<!--</el-link>-->
<!--</el-row>-->
<!--</div>-->
</div>
<!--<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request"-->
<!--ref="jsonpathSuggestList"/>-->
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions" :reloadData="reloadData" style="margin-bottom: 20px"/>
</el-card>
</div>

View File

@ -102,5 +102,7 @@
</script>
<style scoped>
/deep/ .el-tabs__nav-wrap::after {
height: 0px;
}
</style>

View File

@ -50,8 +50,7 @@
import {getUUID, getCurrentUser} from "@/common/js/utils";
import MsResponseText from "../response/ResponseText";
import MsRun from "../Run";
import {createComponent, Request} from "../jmeter/components";
import HeaderManager from "../jmeter/components/configurations/header-manager";
import {createComponent} from "../jmeter/components";
import {REQ_METHOD} from "../../model/JsonData";
import MsRequestResultTail from "../response/RequestResultTail";

View File

@ -1,6 +1,9 @@
<template>
<div :style="customizeStyle">
<el-card>
<div class="el-step__icon is-text" style="color: #015478;background-color: #E6EEF2;margin-right: 10px" v-if="extract.index">
<div class="el-step__icon-inner">{{extract.index}}</div>
</div>
<el-button class="ms-left-buttion" size="small" style="color: #015478;background-color: #E6EEF2">{{$t('api_test.definition.request.extract_param')}}</el-button>
<el-button size="small" style="float: right;margin-top: 0px" @click="remove">移除</el-button>

View File

@ -37,6 +37,7 @@ export default class HTTPSamplerProxy extends Sampler {
this.rest = [];
this.files = [];
this.headers = [];
this.hashTree = [];
}
}

View File

@ -226,5 +226,4 @@
height: 18px;
border-radius: 42%;
}
</style>

View File

@ -1,34 +1,18 @@
<template>
<div class="metric-container">
<el-row type="flex">
<div class="metric">
<div class="value">{{response.responseResult.responseTime}} ms</div>
<div class="name">{{$t('api_report.response_time')}}</div>
<br>
<div class="value">{{response.responseResult.latency}} ms</div>
<div class="name">{{$t('api_report.latency')}}</div>
</div>
<div class="metric">
<div class="value">{{response.requestSize}} bytes</div>
<div class="name">{{$t('api_report.request_size')}}</div>
<br>
<div class="value">{{response.responseResult.responseSize}} bytes</div>
<div class="name">{{$t('api_report.response_size')}}</div>
</div>
<div class="metric horizontal">
<el-row type="flex">
<div class="code">
<div class="value" :class="{'error': error}">{{response.responseResult.responseCode}}</div>
<div class="name">{{$t('api_report.response_code')}}</div>
</div>
<div class="split"></div>
<div class="message">
<div class="value">{{response.responseResult.responseMessage}}</div>
<div class="name">{{$t('api_report.response_message')}}</div>
</div>
</el-row>
</div>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_code')}} :</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseCode ? response.responseResult.responseCode :'0'}}</div>
</el-col>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_time')}} :</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseTime?response.responseResult.responseTime:0}} ms</div>
</el-col>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_size')}} :</div>
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseSize?response.responseResult.responseSize:0}} bytes</div>
</el-col>
</el-row>
</div>
</template>
@ -51,56 +35,6 @@
<style scoped>
.metric-container {
padding: 10px;
}
.metric {
padding: 10px;
border: 1px solid #EBEEF5;
min-width: 120px;
height: 114px;
}
.metric + .metric {
margin-left: 20px;
}
.metric .value {
font-size: 16px;
font-weight: 500;
word-break: break-all;
}
.metric .name {
color: #404040;
opacity: 0.5;
padding: 5px 0;
}
.metric.horizontal {
width: 100%;
}
.metric .code {
min-width: 120px;
}
.metric .code .value {
color: #67C23A;
}
.metric .code .value.error {
color: #F56C6C;
}
.metric .split {
height: 114px;
border-left: 1px solid #EBEEF5;
margin-right: 20px;
}
.metric .message {
max-height: 114px;
overflow-y: auto;
padding-bottom: 20px;
}
</style>

View File

@ -86,7 +86,7 @@
},
computed: {
isSqlType() {
return (this.currentProtocol === "SQL" && this.response.responseResult.responseCode === '200' && this.mode ==='table');
return (this.currentProtocol === "SQL" && this.response.responseResult.responseCode === '200' && this.mode === 'table');
}
}
}
@ -120,6 +120,10 @@
padding: 0;
}
/deep/ .el-tabs__nav-wrap::after {
height: 0px;
}
pre {
margin: 0;
}

View File

@ -9,11 +9,11 @@
<template v-slot:title>{{ $t('commons.project') }}</template>
<search-list ref="projectRecent" :options="projectRecent"/>
<el-divider class="menu-divider"/>
<el-menu-item :index="'/performance/project/create'">
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">创建项目</span>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project'"/>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/api/home'">

View File

@ -2,7 +2,7 @@
<common-monitor-chart>
<template>
<div id="response-time-chart" :style="{ width:'100%',height:'100%' }">
<chart :options="getOptions()" :style="{ width:'100%' }"></chart>
<chart :options="getOptions()" :style="{ width:'100%' }" v-on:click="click"></chart>
</div>
</template>
</common-monitor-chart>
@ -27,20 +27,15 @@ export default {
},
methods: {
click(params) {
//2
if (params.value.substr(0, 1) !== '2') {
let startTime = params.name;
this.result = this.$$get('/api/monitor/getReportId', {'startTime': startTime}, {
'apiUrl': this.apiUrl
}, response => {
this.reportId = response.data;
let reportId = this.reportId
let url = '#/api/report/view/' + reportId;
let target = '_blank';
window.open(url, target);
});
}
let startTime = params.name;
this.result = this.$$get('/api/monitor/getReportId', {'startTime': startTime}, {
'apiUrl': this.apiUrl
}, response => {
this.reportId = response.data;
let reportId = this.reportId
let url = '#/api/report/view/' + reportId;
window.open(url, '_blank');
});
},
getOptions() {
return {

View File

@ -1,4 +1,4 @@
import MsProject from "@/business/components/project/MsProject";
import MsProject from "@/business/components/settings/project/MsProject";
export default {
path: "/api",
@ -24,11 +24,11 @@ export default {
name: "ApiTestList",
component: () => import('@/business/components/api/test/ApiTestList'),
},
{
path: "project/:type",
name: "fucProject",
component: MsProject,
},
// {
// path: "project/:type",
// name: "fucProject",
// component: MsProject,
// },
{
path: "report/list/:testId",
name: "ApiReportList",
@ -53,6 +53,11 @@ export default {
path: "automation/report",
name: "ApiReportList",
component: () => import('@/business/components/api/automation/report/ApiReportList'),
}
},
{
path: 'monitor/view',
name: 'ApiMonitor',
component: () => import('@/business/components/api/monitor/ApiMonitor'),
},
]
}

View File

@ -213,10 +213,4 @@ export default {
margin-bottom: 10px;
}
/deep/ .el-select__tags {
flex-wrap: unset;
overflow: auto;
}
</style>

View File

@ -32,7 +32,7 @@ export default {
options: Object
},
mounted() {
this.recent();
this.init();
},
data() {
return {
@ -44,7 +44,7 @@ export default {
watch: {
search_text(val) {
if (!val) {
this.recent();
this.init();
} else {
this.search();
}
@ -66,7 +66,7 @@ export default {
},
methods: {
recent: function () {
init: function () {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.result = this.$get(this.options.url, (response) => {
this.items = response.data;

View File

@ -9,11 +9,11 @@
<template v-slot:title>{{ $t('commons.project') }}</template>
<search-list ref="projectRecent" :options="projectRecent"/>
<el-divider/>
<el-menu-item :index="'/performance/project/create'">
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">创建项目</span>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project'"/>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/performance/home'">

View File

@ -241,14 +241,9 @@ export default {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = Array.from(this.selectRows).map(row => row.id);
this.result = this.$post('/performance/report/batch/delete', {ids: ids}, () => {
this.selectRows.clear();
this.$success(this.$t('commons.delete_success'));
this.search();
// 广 head
PerformanceEvent.$emit(LIST_CHANGE);
});
this.selectRows.forEach(row => {
this._handleDelete(row);
})
}
}
});

View File

@ -1,4 +1,4 @@
import MsProject from "@/business/components/project/MsProject";
import MsProject from "@/business/components/settings/project/MsProject";
const PerformanceTest = () => import('@/business/components/performance/PerformanceTest')
const PerformanceTestHome = () => import('@/business/components/performance/home/PerformanceTestHome')
@ -43,11 +43,11 @@ export default {
name: "perPlan",
component: PerformanceTestList
},
{
path: "project/:type",
name: "perProject",
component: MsProject
},
// {
// path: "project/:type",
// name: "perProject",
// component: MsProject
// },
{
path: "report/:type",
name: "perReport",

View File

@ -41,7 +41,7 @@
</el-menu-item>
</el-submenu>
<el-menu-item v-for="menu in project" :key="menu.index" :index="menu.index" class="setting-item"
<el-menu-item v-for="menu in project" :key="menu.index" :index="'/setting/project/all'" class="setting-item"
v-permission="menu.roles">
<template v-slot:title>
<font-awesome-icon class="icon" :icon="['fa', 'bars']" size="lg"/>

View File

@ -17,7 +17,7 @@
:cell-style="rowClass"
:header-cell-style="headClass"
>
<el-table-column :label="$t('schedule.event')" min-width="20%" prop="events">
<el-table-column :label="$t('schedule.event')" min-width="15%" prop="events">
<template slot-scope="scope">
<el-select v-model="scope.row.event" :placeholder="$t('organization.message.select_events')" size="mini"
prop="event" :disabled="!scope.row.isSet">
@ -64,8 +64,16 @@
:disabled="!scope.row.isSet||!scope.row.isReadOnly"></el-input>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" min-width="20%" prop="result">
<el-table-column :label="$t('commons.operating')" min-width="25%" prop="result">
<template v-slot:default="scope">
<el-button
type="success"
size="mini"
v-if="scope.row.isSet"
v-xpack
@click="handleTemplate(scope.$index,scope.row)"
>{{ $t('organization.message.template') }}
</el-button>
<el-button
type="primary"
size="mini"
@ -98,14 +106,22 @@
</el-table>
</el-col>
</el-row>
<notice-template v-xpack ref="noticeTemplate"/>
</div>
</template>
<script>
import {hasLicense} from "@/common/js/utils";
const TASK_TYPE = 'DEFECT_TASK';
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./notice/NoticeTemplate.vue") : {};
export default {
name: "DefectTaskNotification",
components: {
"NoticeTemplate": noticeTemplate.default
},
props: {
defectReceiverOptions: {
type: Array
@ -211,6 +227,11 @@ export default {
},
headClass() {
return "text-align:center;background:'#ededed'"
},
handleTemplate(index, row) {
if (hasLicense()) {
this.$refs.noticeTemplate.open(row);
}
}
}
}

View File

@ -11,22 +11,22 @@
<el-row>
<el-col :span="24">
<el-table
:data="jenkinsTask"
class="tb-edit"
border
:cell-style="rowClass"
:header-cell-style="headClass">
<el-table-column :label="$t('schedule.event')" min-width="20%" prop="events">
:data="jenkinsTask"
class="tb-edit"
border
:cell-style="rowClass"
:header-cell-style="headClass">
<el-table-column :label="$t('schedule.event')" min-width="15%" prop="events">
<template slot-scope="scope">
<el-select v-model="scope.row.event"
:placeholder="$t('organization.message.select_events')"
size="mini"
prop="events" :disabled="!scope.row.isSet">
<el-option
v-for="item in jenkinsEventOptions"
:key="item.value"
:label="item.label"
:value="item.value">
v-for="item in jenkinsEventOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
@ -36,10 +36,10 @@
<el-select v-model="row.userIds" filterable multiple size="mini"
:placeholder="$t('commons.please_select')" style="width: 100%;" :disabled="!row.isSet">
<el-option
v-for="item in jenkinsReceiverOptions"
:key="item.id"
:label="item.name"
:value="item.id">
v-for="item in jenkinsReceiverOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</template>
@ -51,10 +51,10 @@
:disabled="!scope.row.isSet" @change="handleEdit(scope.$index, scope.row)"
>
<el-option
v-for="item in receiveTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value">
v-for="item in receiveTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
@ -66,48 +66,65 @@
:disabled="!scope.row.isSet||!scope.row.isReadOnly"></el-input>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" min-width="20%" prop="result">
<el-table-column :label="$t('commons.operating')" min-width="25%" prop="result">
<template v-slot:default="scope">
<el-button
type="primary"
size="mini"
v-show="scope.row.isSet"
@click="handleAddTask(scope.$index,scope.row)"
type="success"
size="mini"
v-if="scope.row.isSet"
v-xpack
@click="handleTemplate(scope.$index,scope.row)"
>{{ $t('organization.message.template') }}
</el-button>
<el-button
type="primary"
size="mini"
v-if="scope.row.isSet"
@click="handleAddTask(scope.$index,scope.row)"
>{{ $t('commons.add') }}
</el-button>
<el-button
size="mini"
v-show="scope.row.isSet"
@click.native.prevent="removeRowTask(scope.$index,jenkinsTask)"
size="mini"
v-if="scope.row.isSet"
@click.native.prevent="removeRowTask(scope.$index,jenkinsTask)"
>{{ $t('commons.cancel') }}
</el-button>
<el-button
type="primary"
size="mini"
v-show="!scope.row.isSet"
@click="handleEditTask(scope.$index,scope.row)"
type="primary"
size="mini"
v-if="!scope.row.isSet"
@click="handleEditTask(scope.$index,scope.row)"
>{{ $t('commons.edit') }}
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
v-show="!scope.row.isSet"
@click.native.prevent="deleteRowTask(scope.$index,scope.row)"
type="danger"
icon="el-icon-delete"
size="mini"
v-show="!scope.row.isSet"
@click.native.prevent="deleteRowTask(scope.$index,scope.row)"
></el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<notice-template v-xpack ref="noticeTemplate"/>
</div>
</template>
<script>
import {hasLicense} from "@/common/js/utils";
const TASK_TYPE = 'JENKINS_TASK';
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./notice/NoticeTemplate.vue") : {};
export default {
name: "JenkinsNotification",
components: {
"NoticeTemplate": noticeTemplate.default
},
props: {
jenkinsReceiverOptions: {
type: Array
@ -187,7 +204,6 @@ export default {
} else {
data.isReadOnly = true;
}
},
addTask(data) {
this.result = this.$post("/notice/save/message/task", data, () => {
@ -215,6 +231,11 @@ export default {
headClass() {
return "text-align:center;background:'#ededed'"
},
handleTemplate(index, row) {
if (hasLicense()) {
this.$refs.noticeTemplate.open(row);
}
}
}
}
</script>

View File

@ -14,12 +14,11 @@
:data="scheduleTask"
class="tb-edit"
border
size="mini"
:cell-style="rowClass"
:header-cell-style="headClass">
<el-table-column :label="$t('schedule.event')" min-width="20%" prop="events">
<el-table-column :label="$t('schedule.event')" prop="events" min-width="15%">
<template slot-scope="scope">
<el-select v-model="scope.row.event"
<el-select v-model="scope.row.event" size="mini"
:placeholder="$t('organization.message.select_events')"
prop="events" :disabled="!scope.row.isSet">
<el-option
@ -33,7 +32,7 @@
</el-table-column>
<el-table-column :label="$t('schedule.receiver')" prop="userIds" min-width="20%">
<template v-slot:default="{row}">
<el-select v-model="row.userIds" filterable multiple
<el-select v-model="row.userIds" filterable multiple size="mini"
:placeholder="$t('commons.please_select')" style="width: 100%;" :disabled="!row.isSet">
<el-option
v-for="item in scheduleReceiverOptions"
@ -44,9 +43,10 @@
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('schedule.receiving_mode')" min-width="20%" prop="type">
<el-table-column :label="$t('schedule.receiving_mode')" prop="type" min-width="15%">
<template slot-scope="scope">
<el-select v-model="scope.row.type" :placeholder="$t('organization.message.select_receiving_method')"
size="mini"
:disabled="!scope.row.isSet" @change="handleEdit(scope.$index, scope.row)"
>
<el-option
@ -60,12 +60,20 @@
</el-table-column>
<el-table-column label="webhook" min-width="20%" prop="webhook">
<template v-slot:default="scope">
<el-input v-model="scope.row.webhook" placeholder="webhook地址"
<el-input v-model="scope.row.webhook" placeholder="webhook地址" size="mini"
:disabled="!scope.row.isSet||!scope.row.isReadOnly"></el-input>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" min-width="20%" prop="result">
<el-table-column :label="$t('commons.operating')" prop="result" min-width="20%">
<template v-slot:default="scope">
<el-button
type="success"
size="mini"
v-if="scope.row.isSet"
v-xpack
@click="handleTemplate(scope.$index,scope.row)"
>{{ $t('organization.message.template') }}
</el-button>
<el-button
type="primary"
size="mini"
@ -102,13 +110,21 @@
</el-table>
</el-col>
</el-row>
<notice-template v-xpack ref="noticeTemplate"/>
</div>
</template>
<script>
import {hasLicense} from "@/common/js/utils";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./notice/NoticeTemplate.vue") : {};
export default {
name: "ScheduleTaskNotification",
components: {
"NoticeTemplate": noticeTemplate.default
},
props: {
testId: String,
scheduleReceiverOptions: Array,
@ -226,6 +242,11 @@ export default {
headClass() {
return "text-align:center;background:'#ededed'"
},
handleTemplate(index, row) {
if (hasLicense()) {
this.$refs.noticeTemplate.open(row);
}
}
}
}
</script>

View File

@ -17,7 +17,7 @@
:cell-style="rowClass"
:header-cell-style="headClass"
>
<el-table-column :label="$t('schedule.event')" min-width="20%" prop="events">
<el-table-column :label="$t('schedule.event')" min-width="15%" prop="events">
<template slot-scope="scope">
<el-select v-model="scope.row.event" :placeholder="$t('organization.message.select_events')"
@change="handleTestPlanReceivers(scope.row)" size="mini"
@ -64,8 +64,16 @@
:disabled="!scope.row.isSet||!scope.row.isReadOnly"></el-input>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" min-width="20%" prop="result">
<el-table-column :label="$t('commons.operating')" min-width="25%" prop="result">
<template v-slot:default="scope">
<el-button
type="success"
size="mini"
v-if="scope.row.isSet"
v-xpack
@click="handleTemplate(scope.$index,scope.row)"
>{{ $t('organization.message.template') }}
</el-button>
<el-button
type="primary"
size="mini"
@ -98,15 +106,23 @@
</el-table>
</el-col>
</el-row>
<notice-template v-xpack ref="noticeTemplate"/>
</div>
</template>
<script>
import {hasLicense} from "@/common/js/utils";
const TASK_TYPE = 'TEST_PLAN_TASK';
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./notice/NoticeTemplate.vue") : {};
export default {
name: "TestPlanTaskNotification",
components: {
"NoticeTemplate": noticeTemplate.default
},
props: {
testPlanReceiverOptions: {
type: Array
@ -231,6 +247,11 @@ export default {
}
row.testPlanReceiverOptions = testPlanReceivers;
},
handleTemplate(index, row) {
if (hasLicense()) {
this.$refs.noticeTemplate.open(row);
}
}
},
watch: {
testPlanReceiverOptions(value) {

View File

@ -17,7 +17,7 @@
:cell-style="rowClass"
:header-cell-style="headClass"
>
<el-table-column :label="$t('schedule.event')" min-width="20%" prop="events">
<el-table-column :label="$t('schedule.event')" min-width="15%" prop="events">
<template slot-scope="scope">
<el-select v-model="scope.row.event" :placeholder="$t('organization.message.select_events')" size="mini"
@change="handleReviewReceivers(scope.row)"
@ -65,8 +65,16 @@
:disabled="!scope.row.isSet||!scope.row.isReadOnly"></el-input>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" min-width="20%" prop="result">
<el-table-column :label="$t('commons.operating')" min-width="25%" prop="result">
<template v-slot:default="scope">
<el-button
type="success"
size="mini"
v-if="scope.row.isSet"
v-xpack
@click="handleTemplate(scope.$index,scope.row)"
>{{ $t('organization.message.template') }}
</el-button>
<el-button
type="primary"
size="mini"
@ -99,14 +107,22 @@
</el-table>
</el-col>
</el-row>
<notice-template v-xpack ref="noticeTemplate"/>
</div>
</template>
<script>
import {hasLicense} from "@/common/js/utils";
const TASK_TYPE = 'REVIEW_TASK';
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./notice/NoticeTemplate.vue") : {};
export default {
name: "TestReviewNotification",
components: {
"NoticeTemplate": noticeTemplate.default
},
props: {
reviewReceiverOptions: {
type: Array
@ -236,6 +252,11 @@ export default {
break;
}
row.reviewReceiverOptions = reviewReceiverOptions;
},
handleTemplate(index, row) {
if (hasLicense()) {
this.$refs.noticeTemplate.open(row);
}
}
},
watch: {

View File

@ -1,53 +1,51 @@
<template>
<ms-container>
<ms-main-container>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<ms-table-header :is-tester-permission="true" :condition.sync="condition" @search="search" @create="create"
:create-tip="btnTips" title=""/>
</template>
<el-table border class="adjust-table" @row-click="link" :data="items" style="width: 100%" @sort-change="sort">
<el-table-column prop="name" :label="$t('commons.name')" width="250" show-overflow-tooltip/>
<el-table-column prop="description" :label="$t('commons.description')" show-overflow-tooltip>
<template v-slot:default="scope">
<pre>{{ scope.row.description }}</pre>
</template>
</el-table-column>
<!--<el-table-column prop="workspaceName" :label="$t('project.owning_workspace')"/>-->
<el-table-column
sortable
prop="createTime"
:label="$t('commons.create_time')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
sortable
prop="updateTime"
:label="$t('commons.update_time')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator :is-tester-permission="true" @editClick="edit(scope.row)"
@deleteClick="handleDelete(scope.row)">
<template v-slot:behind>
<ms-table-operator-button :is-tester-permission="true" :tip="$t('api_test.environment.environment_config')" icon="el-icon-setting"
type="info" @exec="openEnvironmentConfig(scope.row)"/>
</template>
</ms-table-operator>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="list" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
</ms-main-container>
<div v-loading="result.loading">
<el-card class="table-card">
<template v-slot:header>
<ms-table-header :is-tester-permission="true" :condition.sync="condition" @search="search" @create="create"
:create-tip="btnTips" :title="$t('commons.project')"/>
</template>
<el-table border class="adjust-table" @row-click="link" :data="items" style="width: 100%" @sort-change="sort">
<el-table-column prop="name" :label="$t('commons.name')" width="250" show-overflow-tooltip/>
<el-table-column prop="description" :label="$t('commons.description')" show-overflow-tooltip>
<template v-slot:default="scope">
<pre>{{ scope.row.description }}</pre>
</template>
</el-table-column>
<!--<el-table-column prop="workspaceName" :label="$t('project.owning_workspace')"/>-->
<el-table-column
sortable
prop="createTime"
:label="$t('commons.create_time')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
sortable
prop="updateTime"
:label="$t('commons.update_time')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator :is-tester-permission="true" @editClick="edit(scope.row)"
@deleteClick="handleDelete(scope.row)">
<template v-slot:behind>
<ms-table-operator-button :is-tester-permission="true" :tip="$t('api_test.environment.environment_config')" icon="el-icon-setting"
type="info" @exec="openEnvironmentConfig(scope.row)"/>
</template>
</ms-table-operator>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="list" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
<el-dialog :close-on-click-modal="false" :title="title" :visible.sync="createVisible" destroy-on-close @close="handleClose">
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
@ -80,23 +78,23 @@
<api-environment-config ref="environmentConfig"/>
</ms-container>
</div>
</template>
<script>
import MsCreateBox from "../settings/CreateBox";
import MsCreateBox from "../CreateBox";
import {Message} from "element-ui";
import MsTablePagination from "../common/pagination/TablePagination";
import MsTableHeader from "../common/components/MsTableHeader";
import MsTableOperator from "../common/components/MsTableOperator";
import MsDialogFooter from "../common/components/MsDialogFooter";
import MsTablePagination from "../../common/pagination/TablePagination";
import MsTableHeader from "../../common/components/MsTableHeader";
import MsTableOperator from "../../common/components/MsTableOperator";
import MsDialogFooter from "../../common/components/MsDialogFooter";
import {_sort, getCurrentUser, listenGoBack, removeGoBackListener} from "@/common/js/utils";
import MsContainer from "../common/components/MsContainer";
import MsMainContainer from "../common/components/MsMainContainer";
import MsDeleteConfirm from "../common/components/MsDeleteConfirm";
import MsTableOperatorButton from "../common/components/MsTableOperatorButton";
import ApiEnvironmentConfig from "../api/test/components/ApiEnvironmentConfig";
import TemplateComponent from "../track/plan/view/comonents/report/TemplateComponent/TemplateComponent";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsDeleteConfirm from "../../common/components/MsDeleteConfirm";
import MsTableOperatorButton from "../../common/components/MsTableOperatorButton";
import ApiEnvironmentConfig from "../../api/test/components/ApiEnvironmentConfig";
import TemplateComponent from "../../track/plan/view/comonents/report/TemplateComponent/TemplateComponent";
import {ApiEvent, LIST_CHANGE, PerformanceEvent, TrackEvent} from "@/business/components/common/head/ListEvent";
export default {
@ -144,25 +142,13 @@ export default {
if (this.$route.path.split('/')[2] === 'project' &&
this.$route.path.split('/')[3] === 'create') {
this.create();
this.$router.push('/' + this.baseUrl + '/project/all');
this.$router.replace('/setting/project/all');
}
this.list();
},
activated() {
this.list();
},
watch: {
'$route'(to) {
if (this.$route.path.split('/')[2] === 'project' &&
to.path.split('/')[3] === 'create') {
this.create();
this.$router.push('/' + this.baseUrl + '/project/all');
} else if (this.$route.path.split('/')[2] === 'project' &&
to.path.split('/')[3] === 'all') {
this.list();
}
}
},
computed: {
currentUser: () => {
return getCurrentUser();
@ -179,7 +165,7 @@ export default {
return false;
}
this.title = this.$t('project.create');
listenGoBack(this.handleClose);
// listenGoBack(this.handleClose);
this.createVisible = true;
this.form = {};
},
@ -247,6 +233,9 @@ export default {
handleClose() {
removeGoBackListener(this.handleClose);
this.createVisible = false;
this.tapd = false;
this.jira = false;
this.zentao = false;
},
search() {
this.list();

View File

@ -9,7 +9,7 @@
</template>
<script>
import MsProject from "@/business/components/project/MsProject";
import MsProject from "@/business/components/settings/project/MsProject";
export default {
name: "Project",
components: {MsProject},

View File

@ -80,9 +80,9 @@ export default {
}
},
{
path: 'project',
component: () => import('@/business/components/settings/project/Project'),
meta: {project: true, title: '项目管理'}
path: 'project/:type',
component: () => import('@/business/components/settings/project/MsProject'),
meta: {project: true, title: 'project.manager'}
},
]

View File

@ -58,7 +58,6 @@
<script>
import NodeEdit from "./NodeEdit";
import {ROLE_TEST_MANAGER, ROLE_TEST_USER} from "../../../../common/js/constants";
import {checkoutTestManagerOrTestUser, hasRoles} from "../../../../common/js/utils";
export default {

View File

@ -1,7 +1,7 @@
<template>
<div>
<span class="menu-title">{{'[' + title + ']'}}</span>
<el-select filterable slot="prepend" v-model="value" @change="changeData" class="project_menu"
<el-select filterable slot="prepend" v-model="value" @change="changeData" :style="{width: width}"
size="small">
<el-option v-for="(item,index) in data" :key="index" :label="item.name" :value="index"/>
</el-select>
@ -20,6 +20,12 @@
},
title: {
type: String
},
width: {
type: String,
default() {
return "214px";
}
}
},
data() {
@ -43,9 +49,6 @@
</script>
<style scoped>
.project_menu {
width: 214px;
}
.menu-title {
color: darkgrey;

View File

@ -10,11 +10,11 @@
<template v-slot:title>{{ $t('commons.project') }}</template>
<search-list ref="projectRecent" :options="projectRecent"/>
<el-divider/>
<el-menu-item :index="'/performance/project/create'">
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">创建项目</span>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project'"/>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/track/home'">

View File

@ -7,7 +7,7 @@
<related-test-plan-list ref="relatedTestPlanList"/>
</el-row>
<el-row>
<review-list :title="$t('review.my_review')" ref="caseReviewList"/>
<review-list :title="$t('test_track.review.my_review')" ref="caseReviewList"/>
</el-row>
</el-col>
<el-col :span="9">

View File

@ -26,8 +26,8 @@
<script>
import NodeTree from "../../common/NodeTree";
import TestPlanTestCaseList from "./comonents/TestPlanTestCaseList";
import TestCaseRelevance from "./comonents/TestCaseRelevance";
import TestPlanTestCaseList from "./comonents/functional/FunctionalTestCaseList";
import TestCaseRelevance from "./comonents/functional/TestCaseFunctionalRelevance";
import SelectMenu from "../../common/SelectMenu";
import MsContainer from "../../../common/components/MsContainer";
import MsAsideContainer from "../../../common/components/MsAsideContainer";

View File

@ -1,105 +1,169 @@
<template>
<div>
<el-dialog :title="$t('test_track.plan_view.relevance_test_case')"
:visible.sync="dialogFormVisible"
@close="close"
width="60%" v-loading="result.loading"
:close-on-click-modal="false"
top="50px">
<el-container class="main-content">
<el-aside class="tree-aside" width="250px">
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName :
$t('test_track.switch_project') }}
</el-link>
<node-tree class="node-tree"
@nodeSelectEvent="nodeChange"
@refresh="refresh"
:tree-nodes="treeNodes"
ref="nodeTree"/>
</el-aside>
<test-case-relevance-base ref="baseRelevance">
<el-container>
<el-main class="case-content">
<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>
<el-table
:data="testCases"
@filter-change="filter"
row-key="id"
@mouseleave.passive="leave"
v-el-table-infinite-scroll="scrollLoading"
@select-all="handleSelectAll"
@select="handleSelectionChange"
height="50vh"
ref="table">
<template v-slot:aside>
<node-tree class="node-tree"
@nodeSelectEvent="nodeChange"
@refresh="refresh"
:tree-nodes="treeNodes"
ref="nodeTree"/>
</template>
<el-table-column
type="selection"></el-table-column>
<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>
<el-table
:data="testCases"
@filter-change="filter"
row-key="id"
@mouseleave.passive="leave"
v-el-table-infinite-scroll="scrollLoading"
@select-all="handleSelectAll"
@select="handleSelectionChange"
height="50vh"
ref="table">
<el-table-column
prop="name"
:label="$t('test_track.case.name')"
style="width: 100%">
<template v-slot:default="scope">
{{scope.row.name}}
</template>
</el-table-column>
<el-table-column
prop="priority"
:filters="priorityFilters"
column-key="priority"
:label="$t('test_track.case.priority')"
show-overflow-tooltip>
<template v-slot:default="scope">
<priority-table-item :value="scope.row.priority"/>
</template>
</el-table-column>
<el-table-column
prop="type"
:filters="typeFilters"
column-key="type"
:label="$t('test_track.case.type')"
show-overflow-tooltip>
<template v-slot:default="scope">
<type-table-item :value="scope.row.type"/>
</template>
</el-table-column>
</el-table>
<el-table-column
type="selection"></el-table-column>
<div v-if="!lineStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>
<div style="text-align: center"> {{total}} </div>
</el-main>
</el-container>
</el-container>
<el-table-column
prop="name"
:label="$t('test_track.case.name')"
style="width: 100%">
<template v-slot:default="scope">
{{scope.row.name}}
</template>
</el-table-column>
<el-table-column
prop="priority"
:filters="priorityFilters"
column-key="priority"
:label="$t('test_track.case.priority')"
show-overflow-tooltip>
<template v-slot:default="scope">
<priority-table-item :value="scope.row.priority"/>
</template>
</el-table-column>
<el-table-column
prop="type"
:filters="typeFilters"
column-key="type"
:label="$t('test_track.case.type')"
show-overflow-tooltip>
<template v-slot:default="scope">
<type-table-item :value="scope.row.type"/>
</template>
</el-table-column>
</el-table>
<template v-slot:footer>
<ms-dialog-footer @cancel="dialogFormVisible = false" @confirm="saveCaseRelevance"/>
</template>
<div v-if="!lineStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>
<div style="text-align: center"> {{total}} </div>
</el-dialog>
<switch-project ref="switchProject" @getProjectNode="getProjectNode"/>
</div>
</test-case-relevance-base>
<!--<div>-->
<!--<el-dialog :title="$t('test_track.plan_view.relevance_test_case')"-->
<!--:visible.sync="dialogFormVisible"-->
<!--@close="close"-->
<!--width="60%" v-loading="result.loading"-->
<!--:close-on-click-modal="false"-->
<!--top="50px">-->
<!--<el-container class="main-content">-->
<!--<el-aside class="tree-aside" width="250px">-->
<!--<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName :-->
<!--$t('test_track.switch_project') }}-->
<!--</el-link>-->
<!--<node-tree class="node-tree"-->
<!--@nodeSelectEvent="nodeChange"-->
<!--@refresh="refresh"-->
<!--:tree-nodes="treeNodes"-->
<!--ref="nodeTree"/>-->
<!--</el-aside>-->
<!--<el-container>-->
<!--<el-main class="case-content">-->
<!--<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>-->
<!--<el-table-->
<!--:data="testCases"-->
<!--@filter-change="filter"-->
<!--row-key="id"-->
<!--@mouseleave.passive="leave"-->
<!--v-el-table-infinite-scroll="scrollLoading"-->
<!--@select-all="handleSelectAll"-->
<!--@select="handleSelectionChange"-->
<!--height="50vh"-->
<!--ref="table">-->
<!--<el-table-column-->
<!--type="selection"></el-table-column>-->
<!--<el-table-column-->
<!--prop="name"-->
<!--:label="$t('test_track.case.name')"-->
<!--style="width: 100%">-->
<!--<template v-slot:default="scope">-->
<!--{{scope.row.name}}-->
<!--</template>-->
<!--</el-table-column>-->
<!--<el-table-column-->
<!--prop="priority"-->
<!--:filters="priorityFilters"-->
<!--column-key="priority"-->
<!--:label="$t('test_track.case.priority')"-->
<!--show-overflow-tooltip>-->
<!--<template v-slot:default="scope">-->
<!--<priority-table-item :value="scope.row.priority"/>-->
<!--</template>-->
<!--</el-table-column>-->
<!--<el-table-column-->
<!--prop="type"-->
<!--:filters="typeFilters"-->
<!--column-key="type"-->
<!--:label="$t('test_track.case.type')"-->
<!--show-overflow-tooltip>-->
<!--<template v-slot:default="scope">-->
<!--<type-table-item :value="scope.row.type"/>-->
<!--</template>-->
<!--</el-table-column>-->
<!--</el-table>-->
<!--<div v-if="!lineStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>-->
<!--<div style="text-align: center"> {{total}} </div>-->
<!--</el-main>-->
<!--</el-container>-->
<!--</el-container>-->
<!--<template v-slot:footer>-->
<!--<ms-dialog-footer @cancel="dialogFormVisible = false" @confirm="saveCaseRelevance"/>-->
<!--</template>-->
<!--</el-dialog>-->
<!--<switch-project ref="switchProject" @getProjectNode="getProjectNode"/>-->
<!--</div>-->
</template>
<script>
import NodeTree from '../../../common/NodeTree';
import MsDialogFooter from '../../../../common/components/MsDialogFooter'
import PriorityTableItem from "../../../common/tableItems/planview/PriorityTableItem";
import TypeTableItem from "../../../common/tableItems/planview/TypeTableItem";
import {_filter} from "../../../../../../common/js/utils";
import MsTableSearchBar from "../../../../common/components/MsTableSearchBar";
import MsTableAdvSearchBar from "../../../../common/components/search/MsTableAdvSearchBar";
import MsTableHeader from "../../../../common/components/MsTableHeader";
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
import SwitchProject from "../../../case/components/SwitchProject";
import NodeTree from '../../../../common/NodeTree';
import MsDialogFooter from '../../../../../common/components/MsDialogFooter'
import PriorityTableItem from "../../../../common/tableItems/planview/PriorityTableItem";
import TypeTableItem from "../../../../common/tableItems/planview/TypeTableItem";
import MsTableSearchBar from "../../../../../common/components/MsTableSearchBar";
import MsTableAdvSearchBar from "../../../../../common/components/search/MsTableAdvSearchBar";
import MsTableHeader from "../../../../../common/components/MsTableHeader";
import {TEST_CASE_CONFIGS} from "../../../../../common/components/search/search-components";
import SwitchProject from "../../../../case/components/SwitchProject";
import elTableInfiniteScroll from 'el-table-infinite-scroll';
import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase";
import {_filter} from "../../../../../../../common/js/utils";
export default {
name: "TestCaseRelevance",
name: "TestCaseApiRelevance",
components: {
TestCaseRelevanceBase,
NodeTree,
MsDialogFooter,
PriorityTableItem,
@ -168,10 +232,13 @@
this.toggleSelection(this.testCases);
},
methods: {
openTestCaseRelevanceDialog() {
this.getProject();
this.dialogFormVisible = true;
open() {
this.$refs.baseRelevance.open();
//
},
saveCaseRelevance() {
let param = {};
param.planId = this.planId;

View File

@ -10,8 +10,9 @@
:draggable="false"
ref="nodeTree"/>
</template>
<template v-slot:main>
<test-plan-test-case-list
<test-plan-api-case-list
class="table-list"
@openTestCaseRelevanceDialog="openTestCaseRelevanceDialog"
@refresh="refresh"
@ -21,23 +22,28 @@
ref="testPlanTestCaseList"/>
</template>
<test-case-relevance
<test-case-api-relevance
@refresh="refresh"
:plan-id="planId"
ref="testCaseRelevance"/>
</ms-test-plan-common-component>
</template>
<script>
import NodeTree from "../../../../common/NodeTree";
import TestPlanTestCaseList from "../TestPlanTestCaseList";
import TestCaseRelevance from "../TestCaseRelevance";
import MsTestPlanCommonComponent from "../TestPlanCommonComponent";
import TestPlanTestCaseList from "../functional/FunctionalTestCaseList";
import TestCaseRelevance from "../functional/TestCaseFunctionalRelevance";
import MsTestPlanCommonComponent from "../base/TestPlanCommonComponent";
import TestPlanApiCaseList from "./TestPlanApiCaseList";
import TestCaseApiRelevance from "./TestCaseApiRelevance";
export default {
name: "TestPlanApi",
components: {
TestCaseApiRelevance,
TestPlanApiCaseList,
MsTestPlanCommonComponent,
TestCaseRelevance,
TestPlanTestCaseList,
@ -77,7 +83,7 @@
this.getNodeTreeByPlanId();
},
openTestCaseRelevanceDialog() {
this.$refs.testCaseRelevance.openTestCaseRelevanceDialog();
this.$refs.testCaseRelevance.open();
},
nodeChange(nodeIds, pNodes) {
this.selectNodeIds = nodeIds;

View File

@ -213,33 +213,33 @@
</template>
<script>
import ExecutorEdit from './ExecutorEdit';
import StatusEdit from './StatusEdit';
import TestPlanTestCaseEdit from "./TestPlanTestCaseEdit";
import MsTipButton from '../../../../common/components/MsTipButton';
import MsTablePagination from '../../../../common/pagination/TablePagination';
import MsTableHeader from '../../../../common/components/MsTableHeader';
import MsTableButton from '../../../../common/components/MsTableButton';
import NodeBreadcrumb from '../../../common/NodeBreadcrumb';
import ExecutorEdit from '../ExecutorEdit';
import StatusEdit from '../StatusEdit';
import TestPlanTestCaseEdit from "../functional/FunctionalTestCaseEdit";
import MsTipButton from '../../../../../common/components/MsTipButton';
import MsTablePagination from '../../../../../common/pagination/TablePagination';
import MsTableHeader from '../../../../../common/components/MsTableHeader';
import MsTableButton from '../../../../../common/components/MsTableButton';
import NodeBreadcrumb from '../../../../common/NodeBreadcrumb';
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, TokenKey, WORKSPACE_ID} from "@/common/js/constants";
import {_filter, _sort, checkoutTestManagerOrTestUser, hasRoles} from "@/common/js/utils";
import PriorityTableItem from "../../../common/tableItems/planview/PriorityTableItem";
import StatusTableItem from "../../../common/tableItems/planview/StatusTableItem";
import TypeTableItem from "../../../common/tableItems/planview/TypeTableItem";
import MethodTableItem from "../../../common/tableItems/planview/MethodTableItem";
import MsTableOperator from "../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
import TestReportTemplateList from "./TestReportTemplateList";
import TestCaseReportView from "./report/TestCaseReportView";
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
import ShowMoreBtn from "../../../case/components/ShowMoreBtn";
import BatchEdit from "../../../case/components/BatchEdit";
import PriorityTableItem from "../../../../common/tableItems/planview/PriorityTableItem";
import StatusTableItem from "../../../../common/tableItems/planview/StatusTableItem";
import TypeTableItem from "../../../../common/tableItems/planview/TypeTableItem";
import MethodTableItem from "../../../../common/tableItems/planview/MethodTableItem";
import MsTableOperator from "../../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../../common/components/MsTableOperatorButton";
import TestReportTemplateList from "../TestReportTemplateList";
import TestCaseReportView from "../report/TestCaseReportView";
import {TEST_CASE_CONFIGS} from "../../../../../common/components/search/search-components";
import ShowMoreBtn from "../../../../case/components/ShowMoreBtn";
import BatchEdit from "../../../../case/components/BatchEdit";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import {hub} from "@/business/components/track/plan/event-bus";
export default {
name: "TestPlanTestCaseList",
name: "TestPlanApiCaseList",
components: {
TestCaseReportView,
TestReportTemplateList,

View File

@ -0,0 +1,165 @@
<template>
<div>
<el-dialog :title="$t('test_track.plan_view.relevance_test_case')"
:visible.sync="dialogVisible"
@close="close"
width="60%" v-loading="result.loading"
:close-on-click-modal="false"
top="50px">
<el-container class="main-content">
<el-aside class="tree-aside" width="250px">
<select-menu
:data="projects"
width="185px"
:current-data="currentProject"
:title="$t('test_track.plan_view.plan')"
@dataChange="changeProject"/>
<slot name="aside"></slot>
</el-aside>
<el-container>
<el-main class="case-content">
<slot></slot>
</el-main>
</el-container>
</el-container>
<template v-slot:footer>
<ms-dialog-footer @cancel="dialogVisible = false" @confirm="save"/>
</template>
</el-dialog>
</div>
</template>
<script>
import MsDialogFooter from '../../../../../common/components/MsDialogFooter'
import SelectMenu from "../../../../common/SelectMenu";
export default {
name: "TestCaseRelevanceBase",
components: {
SelectMenu,
MsDialogFooter,
},
data() {
return {
result: {},
dialogVisible: false,
currentProject: {},
projectId: '',
projectName: '',
projects: [],
};
},
props: {
planId: {
type: String
}
},
watch: {
},
methods: {
open() {
this.getProject();
this.dialogVisible = true;
},
refreshNode() {
this.$emit('refresh');
},
save() {
this.$emit('save');
},
refresh() {
// this.close();
},
close() {
this.dialogVisible = false;
},
getProject() {
if (this.planId) {
this.result = this.$post("/test/plan/project/", {planId: this.planId}, res => {
let data = res.data;
if (data) {
this.projects = data;
this.projectId = data[0].id;
this.projectName = data[0].name;
this.changeProject(data[0]);
}
})
}
},
changeProject(project) {
this.currentProject = project;
this.$emit('setProject', project.id);
//
this.$emit('refreshNode');
}
}
}
</script>
<style scoped>
.tb-edit .el-input {
display: none;
color: black;
}
.tb-edit .current-row .el-input {
display: block;
}
.tb-edit .current-row .el-input + span {
display: none;
}
.node-tree {
margin-right: 10px;
}
.el-header {
background-color: darkgrey;
color: #333;
line-height: 60px;
}
.case-content {
padding: 0px 20px;
height: 100%;
/*border: 1px solid #EBEEF5;*/
}
.tree-aside {
min-height: 300px;
max-height: 100%;
}
.main-content {
min-height: 300px;
height: 100%;
/*border: 1px solid #EBEEF5;*/
}
.project-link {
float: right;
margin-right: 12px;
margin-bottom: 10px;
}
</style>

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