refactor(接口测试): 开源失败重跑和失败重试及生成测试数据功能
--task=1011041 --user=赵勇 【开源计划】失败重试... https://www.tapd.cn/55049933/s/1327959 --task=1011040 --user=赵勇 【开源计划】自动生成... https://www.tapd.cn/55049933/s/1327961
This commit is contained in:
parent
2e0b010fe2
commit
a0326c34e3
|
@ -0,0 +1,72 @@
|
|||
package io.metersphere.api.dto.definition.request.controller;
|
||||
|
||||
import io.metersphere.plugin.core.MsParameter;
|
||||
import io.metersphere.plugin.core.MsTestElement;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.control.WhileController;
|
||||
import org.apache.jmeter.save.SaveService;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jmeter.visualizers.JSR223Listener;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MsRetryLoopController extends MsTestElement {
|
||||
private String type = "RetryLoopController";
|
||||
private String clazzName = MsRetryLoopController.class.getCanonicalName();
|
||||
|
||||
private long retryNum;
|
||||
|
||||
private String ms_current_timer = UUID.randomUUID().toString();
|
||||
|
||||
@Override
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, MsParameter msParameter) {
|
||||
final HashTree groupTree = controller(tree);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
// 给所有孩子加一个父亲标志
|
||||
el.setParent(this);
|
||||
el.toHashTree(groupTree, el.getHashTree(), msParameter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private WhileController initWhileController(String condition) {
|
||||
if (StringUtils.isEmpty(condition)) {
|
||||
return null;
|
||||
}
|
||||
WhileController controller = new WhileController();
|
||||
controller.setEnabled(this.isEnable());
|
||||
controller.setName("WhileController");
|
||||
controller.setProperty(TestElement.TEST_CLASS, WhileController.class.getName());
|
||||
controller.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("WhileControllerGui"));
|
||||
controller.setCondition(condition);
|
||||
return controller;
|
||||
}
|
||||
|
||||
private String script() {
|
||||
String script = "// 失败重试控制\n" + "try{\n" + "\tint errorCount = prev.getErrorCount();\n" + "\tif(errorCount == 0 && prev.getFirstAssertionFailureMessage() == null ){\n" + "\t vars.put(\"" + ms_current_timer + "\", \"stop\");\n" + "\t}\n" + "\tif(vars.get(\"" + ms_current_timer + "_num\") == null){\n" + "\t\tvars.put(\"" + ms_current_timer + "_num\", \"0\");\n" + "\t}else{\n" + "\t\tint retryNum= Integer.parseInt(vars.get(\"" + ms_current_timer + "_num\"));\n" + "\t\tlog.info(\"重试:\"+ retryNum);\n" + " \tprev.setSampleLabel(\"MsRetry_\"+ (retryNum + 1) + \"_\" + prev.getSampleLabel());\n" + "\t\tretryNum =retryNum +1;\n" + "\t\tvars.put(\"" + ms_current_timer + "_num\",retryNum + \"\");\n" + "\t}\n" + "\tif(vars.get(\"" + ms_current_timer + "_num\").equals( \"" + retryNum + "\")){\n" + "\t\tvars.put(\"" + ms_current_timer + "\", \"stop\");\n" + "\t}\n" + "}catch (Exception e){\n" + "\tvars.put(\"" + ms_current_timer + "\", \"stop\");\n" + "}\n";
|
||||
return script;
|
||||
}
|
||||
|
||||
private HashTree controller(HashTree tree) {
|
||||
String whileCondition = "${__jexl3(" + "\"${" + ms_current_timer + "}\" !=\"stop\")}";
|
||||
HashTree hashTree = tree.add(initWhileController(whileCondition));
|
||||
// 添加超时处理,防止死循环
|
||||
JSR223Listener postProcessor = new JSR223Listener();
|
||||
postProcessor.setName("Retry-controller");
|
||||
postProcessor.setProperty(TestElement.TEST_CLASS, JSR223Listener.class.getName());
|
||||
postProcessor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
|
||||
postProcessor.setProperty("scriptLanguage", "beanshell");
|
||||
postProcessor.setProperty("script", script());
|
||||
hashTree.add(postProcessor);
|
||||
return hashTree;
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ import io.metersphere.plugin.core.MsTestElement;
|
|||
import io.metersphere.service.ApiExecutionQueueService;
|
||||
import io.metersphere.service.RemakeReportService;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import io.metersphere.xpack.api.service.ApiRetryOnFailureService;
|
||||
import io.metersphere.service.ApiRetryOnFailureService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.json.JSONObject;
|
||||
|
@ -59,6 +59,8 @@ public class ApiCaseSerialService {
|
|||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private TestPlanApiCaseMapper testPlanApiCaseMapper;
|
||||
@Resource
|
||||
private ApiRetryOnFailureService apiRetryOnFailureService;
|
||||
|
||||
public void serial(DBTestQueue executionQueue) {
|
||||
ApiExecutionQueueDetail queue = executionQueue.getDetail();
|
||||
|
@ -154,7 +156,6 @@ public class ApiCaseSerialService {
|
|||
String data = element.toString();
|
||||
if (runRequest.isRetryEnable() && runRequest.getRetryNum() > 0) {
|
||||
// 失败重试
|
||||
ApiRetryOnFailureService apiRetryOnFailureService = CommonBeanFactory.getBean(ApiRetryOnFailureService.class);
|
||||
String retryData = apiRetryOnFailureService.retry(data, runRequest.getRetryNum(), true);
|
||||
data = StringUtils.isNotEmpty(retryData) ? retryData : data;
|
||||
// 格式化数据
|
||||
|
|
|
@ -23,7 +23,7 @@ import io.metersphere.service.ApiExecutionQueueService;
|
|||
import io.metersphere.service.RemakeReportService;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import io.metersphere.vo.BooleanPool;
|
||||
import io.metersphere.xpack.api.service.ApiRetryOnFailureService;
|
||||
import io.metersphere.service.ApiRetryOnFailureService;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package io.metersphere.controller;
|
||||
|
||||
import io.metersphere.service.TestDataGenerator;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/test/data")
|
||||
public class TestDataController {
|
||||
@PostMapping("/generator")
|
||||
public String preview(@RequestBody String jsonSchema) {
|
||||
return TestDataGenerator.generator(jsonSchema);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import io.metersphere.api.dto.definition.request.controller.MsRetryLoopController;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
import io.metersphere.commons.utils.JSONUtil;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.plugin.core.MsTestElement;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class ApiRetryOnFailureService {
|
||||
public final static List<String> requests = List.of(
|
||||
"HTTPSamplerProxy",
|
||||
"DubboSampler",
|
||||
"JDBCSampler",
|
||||
"TCPSampler",
|
||||
"JSR223Processor");
|
||||
|
||||
private final static String HASH_TREE_ELEMENT = "hashTree";
|
||||
private final static String TYPE = "type";
|
||||
private final static String RESOURCE_ID = "resourceId";
|
||||
private final static String RETRY = "MsRetry_";
|
||||
private final static String LOOP = "LoopController";
|
||||
|
||||
public String retry(String data, long retryNum, boolean isCase) {
|
||||
if (StringUtils.isNotEmpty(data)) {
|
||||
JSONObject element = JSONUtil.parseObject(data);
|
||||
if (element != null && isCase) {
|
||||
return formatSampler(element, retryNum).toString();
|
||||
}
|
||||
if (element != null && element.has(HASH_TREE_ELEMENT) && !StringUtils.equalsIgnoreCase(element.optString(TYPE), LOOP)) {
|
||||
JSONArray hashTree = element.getJSONArray(HASH_TREE_ELEMENT);
|
||||
setRetry(hashTree, retryNum);
|
||||
}
|
||||
return element.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MsTestElement retryParse(String data) {
|
||||
try {
|
||||
MsRetryLoopController controller = JSON.parseObject(data, MsRetryLoopController.class);
|
||||
return controller;
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setRetry(JSONArray hashTree, long retryNum) {
|
||||
for (int i = 0; i < hashTree.length(); i++) {
|
||||
JSONObject element = hashTree.getJSONObject(i);
|
||||
if (StringUtils.equalsIgnoreCase(element.optString(TYPE), LOOP)) {
|
||||
continue;
|
||||
}
|
||||
JSONObject whileObj = formatSampler(element, retryNum);
|
||||
if (whileObj != null) {
|
||||
hashTree.put(i, whileObj);
|
||||
} else if (element.has(HASH_TREE_ELEMENT)) {
|
||||
JSONArray elementJSONArray = element.getJSONArray(HASH_TREE_ELEMENT);
|
||||
setRetry(elementJSONArray, retryNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject formatSampler(JSONObject element, long retryNum) {
|
||||
if (element.has(TYPE) && requests.contains(element.optString(TYPE))) {
|
||||
MsRetryLoopController loopController = new MsRetryLoopController();
|
||||
loopController.setClazzName(MsRetryLoopController.class.getCanonicalName());
|
||||
loopController.setName(RETRY + element.optString(RESOURCE_ID));
|
||||
loopController.setRetryNum(retryNum);
|
||||
loopController.setEnable(true);
|
||||
loopController.setResourceId(UUID.randomUUID().toString());
|
||||
|
||||
JSONObject whileObj = JSONUtil.parseObject(JSON.toJSONString(loopController));
|
||||
JSONArray hashTree = new JSONArray();
|
||||
hashTree.put(element);
|
||||
|
||||
whileObj.put(HASH_TREE_ELEMENT, hashTree);
|
||||
return whileObj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -201,7 +201,7 @@ public class MsHashTreeService {
|
|||
private JSONObject setRefScenario(JSONObject element) {
|
||||
boolean enable = element.has(ENABLE) ? element.optBoolean(ENABLE) : true;
|
||||
if (!element.has(MIX_ENABLE)) {
|
||||
element.put(MIX_ENABLE, true);
|
||||
element.put(MIX_ENABLE, false);
|
||||
}
|
||||
|
||||
ApiScenarioDTO scenarioWithBLOBs = extApiScenarioMapper.selectById(element.optString(ID));
|
||||
|
@ -209,7 +209,7 @@ public class MsHashTreeService {
|
|||
boolean environmentEnable = element.has(ENV_ENABLE) ? element.optBoolean(ENV_ENABLE) : false;
|
||||
boolean variableEnable = element.has(VARIABLE_ENABLE) ? element.optBoolean(VARIABLE_ENABLE) : false;
|
||||
boolean mixEnable = element.has(MIX_ENABLE)
|
||||
? element.getBoolean(MIX_ENABLE) : true;
|
||||
? element.getBoolean(MIX_ENABLE) : false;
|
||||
|
||||
if (environmentEnable && StringUtils.isNotEmpty(scenarioWithBLOBs.getEnvironmentJson())) {
|
||||
element.put(ENV_MAP, JSON.parseObject(scenarioWithBLOBs.getEnvironmentJson(), Map.class));
|
||||
|
|
|
@ -0,0 +1,345 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import com.apifan.common.random.source.DateTimeSource;
|
||||
import com.apifan.common.random.source.InternetSource;
|
||||
import com.apifan.common.random.source.NumberSource;
|
||||
import com.google.gson.*;
|
||||
import com.mifmif.common.regex.Generex;
|
||||
import io.metersphere.jmeter.utils.ScriptEngineUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 生成测试数据
|
||||
*/
|
||||
public class TestDataGenerator {
|
||||
private static final String TYPE = "type";
|
||||
private static final String ALL_OF = "allOf";
|
||||
private static final String DEFINITIONS = "definitions";
|
||||
private static final String PROPERTIES = "properties";
|
||||
private static final String ARRAY = "array";
|
||||
private static final String OBJECT = "object";
|
||||
private static final String MS_OBJECT = "MS-OBJECT";
|
||||
private static final String ITEMS = "items";
|
||||
private static final String ENUM = "enum";
|
||||
private static final String STRING = "string";
|
||||
private static final String DEFAULT = "default";
|
||||
private static final String MOCK = "mock";
|
||||
private static final String MAXLENGTH = "maxLength";
|
||||
private static final String MINLENGTH = "minLength";
|
||||
private static final String FORMAT = "format";
|
||||
private static final String PATTERN = "pattern";
|
||||
private static final String INTEGER = "integer";
|
||||
private static final String NUMBER = "number";
|
||||
private static final String BOOLEAN = "boolean";
|
||||
private static final String MINIMUM = "minimum";
|
||||
private static final String MAXIMUM = "maximum";
|
||||
|
||||
public static void analyzeSchema(String json, JSONObject rootObj) {
|
||||
Gson gson = new Gson();
|
||||
JsonElement element = gson.fromJson(json, JsonElement.class);
|
||||
JsonObject rootElement = element.getAsJsonObject();
|
||||
analyzeRootSchemaElement(rootElement, rootObj);
|
||||
}
|
||||
|
||||
public static void analyzeRootSchemaElement(JsonObject rootElement, JSONObject rootObj) {
|
||||
if ((rootElement.has(TYPE) || rootElement.has(ALL_OF)) && rootElement != null) {
|
||||
analyzeObject(rootElement, rootObj);
|
||||
}
|
||||
if (rootElement.has(DEFINITIONS)) {
|
||||
analyzeDefinitions(rootElement);
|
||||
}
|
||||
}
|
||||
|
||||
public static void analyzeObject(JsonObject object, JSONObject rootObj) {
|
||||
if (object.has(ALL_OF)) {
|
||||
for (JsonElement el : object.get(ALL_OF).getAsJsonArray()) {
|
||||
JsonObject elObj = el.getAsJsonObject();
|
||||
if (elObj.has(PROPERTIES)) {
|
||||
analyzeProperties(rootObj, elObj);
|
||||
}
|
||||
}
|
||||
} else if (object.has(PROPERTIES)) {
|
||||
analyzeProperties(rootObj, object);
|
||||
} else if (object.has(TYPE) && object.get(TYPE).getAsString().equals(ARRAY)) {
|
||||
analyzeProperty(rootObj, MS_OBJECT, object);
|
||||
} else if (object.has(TYPE) && !object.get(TYPE).getAsString().equals(OBJECT)) {
|
||||
analyzeProperty(rootObj, object.getAsString(), object);
|
||||
}
|
||||
}
|
||||
|
||||
private static void analyzeProperties(JSONObject rootObj, JsonObject allOfElementObj) {
|
||||
JsonObject propertiesObj = allOfElementObj.get(PROPERTIES).getAsJsonObject();
|
||||
for (Entry<String, JsonElement> entry : propertiesObj.entrySet()) {
|
||||
String propertyKey = entry.getKey();
|
||||
JsonObject propertyObj = propertiesObj.get(propertyKey).getAsJsonObject();
|
||||
analyzeProperty(rootObj, propertyKey, propertyObj);
|
||||
}
|
||||
}
|
||||
|
||||
public static void analyzeItems(JSONObject concept, String propertyName, JsonObject object) {
|
||||
// 先设置空值
|
||||
List<Object> array = new LinkedList<>();
|
||||
JsonArray jsonArray = new JsonArray();
|
||||
if (object.has(ITEMS) && object.get(ITEMS).isJsonArray()) {
|
||||
jsonArray = object.get(ITEMS).getAsJsonArray();
|
||||
} else {
|
||||
JsonObject itemsObject = object.get(ITEMS).getAsJsonObject();
|
||||
array.add(itemsObject);
|
||||
}
|
||||
|
||||
for (JsonElement element : jsonArray) {
|
||||
JsonObject itemsObject = element.getAsJsonObject();
|
||||
if (object.has(ITEMS)) {
|
||||
if (itemsObject.has(ENUM)) {
|
||||
array.add(analyzeEnumProperty(itemsObject));
|
||||
} else if (itemsObject.has(TYPE) && itemsObject.get(TYPE).getAsString().equals(STRING)) {
|
||||
array.add(analyzeString(itemsObject));
|
||||
} else if (itemsObject.has(PROPERTIES)) {
|
||||
JSONObject propertyConcept = new JSONObject();
|
||||
analyzeProperties(propertyConcept, itemsObject);
|
||||
array.add(propertyConcept);
|
||||
} else if (itemsObject.has(TYPE) && itemsObject.get(TYPE) instanceof JsonPrimitive) {
|
||||
JSONObject newJsonObj = new JSONObject();
|
||||
analyzeProperty(newJsonObj, propertyName + "_item", itemsObject);
|
||||
array.add(newJsonObj.get(propertyName + "_item"));
|
||||
}
|
||||
} else if (object.has(ITEMS) && object.get(ITEMS).isJsonArray()) {
|
||||
JsonArray itemsObjectArray = object.get(ITEMS).getAsJsonArray();
|
||||
array.add(itemsObjectArray);
|
||||
}
|
||||
}
|
||||
concept.put(propertyName, array);
|
||||
}
|
||||
|
||||
public static String getMockValue(JsonObject object) {
|
||||
if (object.has(MOCK)
|
||||
&& object.get(MOCK).getAsJsonObject() != null
|
||||
&& object.get(MOCK).getAsJsonObject().get(MOCK) != null
|
||||
&& StringUtils.isNotBlank(object.get(MOCK).getAsJsonObject().get(MOCK).getAsString())) {
|
||||
if (StringUtils.startsWithAny(object.get(MOCK).getAsJsonObject().get(MOCK).getAsString(), "@", "${")) {
|
||||
return ScriptEngineUtils.calculate(object.get(MOCK).getAsJsonObject().get(MOCK).getAsString());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String analyzeString(JsonObject object) {
|
||||
// 先设置空值
|
||||
if (object.has(DEFAULT)) {
|
||||
return object.get(DEFAULT).getAsString();
|
||||
}
|
||||
Object mockValue = getMockValue(object);
|
||||
if (mockValue != null) {
|
||||
return mockValue.toString();
|
||||
}
|
||||
int maxLength = 9;
|
||||
if (object.has(MAXLENGTH)) {
|
||||
maxLength = object.get(MAXLENGTH).getAsInt();
|
||||
}
|
||||
int minLength = 0;
|
||||
if (object.has(MINLENGTH)) {
|
||||
minLength = object.get(MINLENGTH).getAsInt();
|
||||
}
|
||||
String value = RandomStringUtils.randomAlphanumeric(minLength, maxLength);
|
||||
Object enumObj = analyzeEnumProperty(object);
|
||||
String v = enumObj == null ? "" : String.valueOf(enumObj);
|
||||
value = StringUtils.isNotBlank(v) ? v : value;
|
||||
try {
|
||||
if (object.has(FORMAT)) {
|
||||
String propertyFormat = object.get(FORMAT).getAsString();
|
||||
switch (propertyFormat) {
|
||||
case "date-time":
|
||||
value = DateTimeSource.getInstance().randomTimestamp(LocalDate.now()) + "";
|
||||
break;
|
||||
case "date":
|
||||
value = DateTimeSource.getInstance().randomDate(LocalDate.now().getYear(), "yyyy-MM-dd");
|
||||
break;
|
||||
case "email":
|
||||
value = InternetSource.getInstance().randomEmail(maxLength);
|
||||
break;
|
||||
case "hostname":
|
||||
value = InternetSource.getInstance().randomDomain(maxLength);
|
||||
break;
|
||||
case "ipv4":
|
||||
value = InternetSource.getInstance().randomPublicIpv4();
|
||||
break;
|
||||
case "ipv6":
|
||||
value = InternetSource.getInstance().randomIpV6();
|
||||
break;
|
||||
case "uri":
|
||||
value = InternetSource.getInstance().randomStaticUrl("jpg");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (object.has(PATTERN)) {
|
||||
String pattern = object.get(PATTERN).getAsString();
|
||||
if (StringUtils.isNotEmpty(pattern)) {
|
||||
Generex generex = new Generex(pattern);
|
||||
value = generex.random();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} catch (Exception e) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isNumber(Object obj) {
|
||||
if (ObjectUtils.isEmpty(obj)) {
|
||||
return false;
|
||||
}
|
||||
Pattern pattern = Pattern.compile("-?[0-9]+\\.?[0-9]*");
|
||||
Matcher isNum = pattern.matcher(obj.toString());
|
||||
if (!isNum.matches()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Object analyzeInteger(JsonObject object) {
|
||||
// 先设置空值
|
||||
if (object.has(DEFAULT) && isNumber(object.get(DEFAULT))) {
|
||||
return object.get(DEFAULT).getAsInt();
|
||||
}
|
||||
Object mockValue = getMockValue(object);
|
||||
if (mockValue != null && isNumber(mockValue)) {
|
||||
return Integer.parseInt(mockValue.toString());
|
||||
}
|
||||
int minimum = 1;
|
||||
int maximum = 101;
|
||||
if (object.has(MINIMUM)) {
|
||||
minimum = object.get(MINIMUM).getAsInt() < 0 ? 0 : object.get(MINIMUM).getAsInt();
|
||||
}
|
||||
if (object.has(MAXIMUM)) {
|
||||
maximum = object.get(MAXIMUM).getAsInt();
|
||||
}
|
||||
return NumberSource.getInstance().randomInt(minimum, maximum);
|
||||
}
|
||||
|
||||
public static Object analyzeNumber(JsonObject object) {
|
||||
if (object != null && object.has(DEFAULT) && isNumber(object.has(DEFAULT))) {
|
||||
return object.get(DEFAULT).getAsFloat();
|
||||
}
|
||||
Object mockValue = getMockValue(object);
|
||||
if (mockValue != null && isNumber(mockValue)) {
|
||||
return Float.parseFloat(mockValue.toString());
|
||||
}
|
||||
float maximum = 200001.0f;
|
||||
float minimum = 100000.0f;
|
||||
if (object.has(MINIMUM)) {
|
||||
float min = object.get(MINIMUM).getAsFloat();
|
||||
minimum = min < 0 ? 0 : min;
|
||||
}
|
||||
if (object.has(MAXIMUM)) {
|
||||
float max = object.get(MAXIMUM).getAsFloat();
|
||||
maximum = max > 0 ? max : maximum;
|
||||
}
|
||||
return NumberSource.getInstance().randomDouble(minimum, maximum);
|
||||
}
|
||||
|
||||
public static boolean getRandomBoolean() {
|
||||
return Math.random() < 0.5;
|
||||
}
|
||||
|
||||
public static Boolean analyzeBoolean(JsonObject object) {
|
||||
if (object.has(DEFAULT)) {
|
||||
return object.get(DEFAULT).getAsBoolean();
|
||||
}
|
||||
return getRandomBoolean();
|
||||
}
|
||||
|
||||
public static void analyzeProperty(JSONObject concept, String propertyName, JsonObject object) {
|
||||
if (object != null && object.has(TYPE)) {
|
||||
String propertyObjType = getPropertyObjType(object);
|
||||
if (object.has(DEFAULT)) {
|
||||
concept.put(propertyName, object.get(DEFAULT).getAsString());
|
||||
} else if (object.has(ENUM)) {
|
||||
concept.put(propertyName, analyzeEnumProperty(object));
|
||||
} else if (propertyObjType.equals(STRING)) {
|
||||
concept.put(propertyName, analyzeString(object));
|
||||
} else if (propertyObjType.equals(INTEGER)) {
|
||||
concept.put(propertyName, analyzeInteger(object));
|
||||
} else if (propertyObjType.equals(NUMBER)) {
|
||||
concept.put(propertyName, analyzeNumber(object));
|
||||
} else if (propertyObjType.equals(BOOLEAN)) {
|
||||
concept.put(propertyName, analyzeBoolean(object));
|
||||
} else if (propertyObjType.equals(ARRAY)) {
|
||||
analyzeItems(concept, propertyName, object);
|
||||
} else if (propertyObjType.equals(OBJECT)) {
|
||||
JSONObject obj = new JSONObject();
|
||||
concept.put(propertyName, obj);
|
||||
analyzeObject(object, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPropertyObjType(JsonObject object) {
|
||||
if (object.get(TYPE) != null && object.get(TYPE) instanceof JsonPrimitive) {
|
||||
return object.get(TYPE).getAsString();
|
||||
} else if (object.get(TYPE) instanceof JsonArray) {
|
||||
JsonArray typeArray = object.get(TYPE).getAsJsonArray();
|
||||
return typeArray.get(0).getAsString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object analyzeEnumProperty(JsonObject object) {
|
||||
Object enumValue = "";
|
||||
try {
|
||||
if (object.get(ENUM) != null) {
|
||||
String enums = object.get(ENUM).getAsString();
|
||||
if (StringUtils.isNotBlank(enums)) {
|
||||
String enumArr[] = enums.split("\n");
|
||||
int index = (int) (Math.random() * enumArr.length);
|
||||
enumValue = enumArr[index];
|
||||
}
|
||||
}
|
||||
String propertyObjType = getPropertyObjType(object);
|
||||
if (propertyObjType.equals(INTEGER)) {
|
||||
enumValue = Integer.parseInt(enumValue.toString());
|
||||
} else if (propertyObjType.equals(NUMBER)) {
|
||||
enumValue = Float.parseFloat(enumValue.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return enumValue;
|
||||
}
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
public static void analyzeDefinitions(JsonObject object) {
|
||||
JsonObject definitionsObj = object.get(DEFINITIONS).getAsJsonObject();
|
||||
for (Entry<String, JsonElement> entry : definitionsObj.entrySet()) {
|
||||
String definitionKey = entry.getKey();
|
||||
JsonObject definitionObj = definitionsObj.get(definitionKey).getAsJsonObject();
|
||||
JSONObject obj = new JSONObject();
|
||||
analyzeRootSchemaElement(definitionObj, obj);
|
||||
}
|
||||
}
|
||||
|
||||
public static String generator(String jsonSchema) {
|
||||
try {
|
||||
if (StringUtils.isEmpty(jsonSchema)) {
|
||||
return null;
|
||||
}
|
||||
JSONObject root = new JSONObject();
|
||||
analyzeSchema(jsonSchema, root);
|
||||
// 格式化返回
|
||||
if (root != null && root.has(MS_OBJECT)) {
|
||||
return root.get(MS_OBJECT).toString();
|
||||
}
|
||||
return root.toString();
|
||||
} catch (Exception ex) {
|
||||
return jsonSchema;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -118,7 +118,6 @@ import {apiProjectByScenarioId, getProjectApplicationConfig} from '../../../api/
|
|||
import { apiTestReRun } from '../../../api/xpack';
|
||||
import { getUUID } from 'metersphere-frontend/src/utils';
|
||||
import { getApiScenarioIdByPlanScenarioId } from '@/api/test-plan';
|
||||
import {getScenarioReport} from '../../../api/scenario-report';
|
||||
|
||||
export default {
|
||||
name: 'MsApiReportViewHeader',
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
@click.stop
|
||||
@click="generate"
|
||||
style="margin-left: 10px"
|
||||
v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API') && hasLicense()">
|
||||
v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API')">
|
||||
{{ $t('commons.generate_test_data') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
|
|
@ -111,9 +111,7 @@
|
|||
:headers="request.headers"
|
||||
:body="request.body" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
name="create"
|
||||
v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API') && hasLicense() && definitionTest">
|
||||
<el-tab-pane name="create" v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API') && definitionTest">
|
||||
<template v-slot:label>
|
||||
<el-button size="mini" type="primary" @click.stop @click="generate"
|
||||
>{{ $t('commons.generate_test_data') }}
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
@click.stop
|
||||
@click="generate"
|
||||
style="margin-left: 10px"
|
||||
v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API') && hasLicense()">
|
||||
v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API')">
|
||||
{{ $t('commons.generate_test_data') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package io.metersphere.xpack.api.service;
|
||||
|
||||
import io.metersphere.plugin.core.MsTestElement;
|
||||
|
||||
public interface ApiRetryOnFailureService {
|
||||
public String retry(String data, long retryNum, boolean isCase);
|
||||
|
||||
public MsTestElement retryParse(String retryCase);
|
||||
|
||||
}
|
|
@ -117,7 +117,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 失败重试 -->
|
||||
<div class="mode-row" v-if="isHasLicense">
|
||||
<div class="mode-row">
|
||||
<el-checkbox
|
||||
v-model="runConfig.retryEnable"
|
||||
class="ms-failure-div-right"
|
||||
|
@ -127,7 +127,7 @@
|
|||
<span v-if="runConfig.retryEnable">
|
||||
<el-tooltip placement="top" style="margin: 0 4px 0 2px">
|
||||
<div slot="content">{{ $t("run_mode.retry_message") }}</div>
|
||||
<i class="el-icon-question" style="cursor: pointer"/>
|
||||
<i class="el-icon-question" style="cursor: pointer" />
|
||||
</el-tooltip>
|
||||
<span style="margin-left: 10px">
|
||||
{{ $t("run_mode.retry") }}
|
||||
|
@ -146,9 +146,8 @@
|
|||
</div>
|
||||
|
||||
<div class="mode-row" v-if="runConfig.mode === 'serial'">
|
||||
<el-checkbox v-model="runConfig.onSampleError">{{
|
||||
$t("api_test.fail_to_stop")
|
||||
}}
|
||||
<el-checkbox v-model="runConfig.onSampleError"
|
||||
>{{ $t("api_test.fail_to_stop") }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
|
@ -166,44 +165,43 @@
|
|||
<el-button @click="close">{{ $t("commons.cancel") }}</el-button>
|
||||
<el-dropdown @command="handleCommand" style="margin-left: 5px">
|
||||
<el-button type="primary">
|
||||
{{
|
||||
$t("load_test.save_and_run")
|
||||
{{ $t("load_test.save_and_run")
|
||||
}}<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="run">{{
|
||||
$t("load_test.save_and_run")
|
||||
}}
|
||||
<el-dropdown-item command="run"
|
||||
>{{ $t("load_test.save_and_run") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="save">{{
|
||||
$t("commons.save")
|
||||
}}
|
||||
<el-dropdown-item command="save"
|
||||
>{{ $t("commons.save") }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch"/>
|
||||
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
|
||||
import {hasLicense} from "metersphere-frontend/src/utils/permission";
|
||||
import {strMapToObj} from "metersphere-frontend/src/utils";
|
||||
import { strMapToObj } from "metersphere-frontend/src/utils";
|
||||
|
||||
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
|
||||
import {getCurrentProjectID, getOwnerProjects} from "@/business/utils/sdk-utils";
|
||||
import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
|
||||
import { ENV_TYPE } from "metersphere-frontend/src/utils/constants";
|
||||
import {
|
||||
getCurrentProjectID,
|
||||
getOwnerProjects,
|
||||
} from "@/business/utils/sdk-utils";
|
||||
import { getQuotaValidResourcePools } from "@/api/remote/resource-pool";
|
||||
import EnvGroupPopover from "@/business/plan/env/EnvGroupPopover";
|
||||
import {getApiCaseEnv} from "@/api/remote/plan/test-plan-api-case";
|
||||
import {getApiScenarioEnv, getPlanCaseEnv} from "@/api/remote/plan/test-plan";
|
||||
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
|
||||
import {getProjectConfig} from "@/api/project";
|
||||
import { getApiCaseEnv } from "@/api/remote/plan/test-plan-api-case";
|
||||
import { getApiScenarioEnv, getPlanCaseEnv } from "@/api/remote/plan/test-plan";
|
||||
import { getSystemBaseSetting } from "metersphere-frontend/src/api/system";
|
||||
import { getProjectConfig } from "@/api/project";
|
||||
|
||||
export default {
|
||||
name: "MsPlanRunModeWithEnv",
|
||||
components: {EnvGroupPopover, MsDialogFooter},
|
||||
components: { EnvGroupPopover, MsDialogFooter },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
@ -227,7 +225,6 @@ export default {
|
|||
retryNum: 1,
|
||||
browser: "CHROME",
|
||||
},
|
||||
isHasLicense: hasLicense(),
|
||||
projectEnvListMap: {},
|
||||
projectList: [],
|
||||
projectIds: new Set(),
|
||||
|
@ -274,8 +271,12 @@ export default {
|
|||
open(testType, runModeConfig) {
|
||||
if (runModeConfig) {
|
||||
this.runConfig = JSON.parse(runModeConfig);
|
||||
this.runConfig.onSampleError = this.runConfig.onSampleError === 'true' || this.runConfig.onSampleError === true;
|
||||
this.runConfig.runWithinResourcePool = this.runConfig.runWithinResourcePool === 'true' || this.runConfig.runWithinResourcePool === true;
|
||||
this.runConfig.onSampleError =
|
||||
this.runConfig.onSampleError === "true" ||
|
||||
this.runConfig.onSampleError === true;
|
||||
this.runConfig.runWithinResourcePool =
|
||||
this.runConfig.runWithinResourcePool === "true" ||
|
||||
this.runConfig.runWithinResourcePool === true;
|
||||
}
|
||||
this.runModeVisible = true;
|
||||
this.testType = testType;
|
||||
|
@ -285,21 +286,21 @@ export default {
|
|||
},
|
||||
query() {
|
||||
this.loading = true;
|
||||
this.result = getSystemBaseSetting().then(response => {
|
||||
this.result = getSystemBaseSetting().then((response) => {
|
||||
if (!response.data.runMode) {
|
||||
response.data.runMode = 'LOCAL'
|
||||
response.data.runMode = "LOCAL";
|
||||
}
|
||||
this.runMode = response.data.runMode;
|
||||
if (this.runMode === 'POOL') {
|
||||
if (this.runMode === "POOL") {
|
||||
this.runConfig.runWithinResourcePool = true;
|
||||
this.getProjectApplication();
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
getProjectApplication() {
|
||||
getProjectConfig(getCurrentProjectID(), "").then(res => {
|
||||
getProjectConfig(getCurrentProjectID(), "").then((res) => {
|
||||
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
|
||||
this.runConfig.resourcePoolId = res.data.resourcePoolId;
|
||||
}
|
||||
|
@ -329,10 +330,9 @@ export default {
|
|||
this.close();
|
||||
},
|
||||
getResourcePools() {
|
||||
getQuotaValidResourcePools()
|
||||
.then((response) => {
|
||||
this.resourcePools = response.data;
|
||||
});
|
||||
getQuotaValidResourcePools().then((response) => {
|
||||
this.resourcePools = response.data;
|
||||
});
|
||||
},
|
||||
setProjectEnvMap(projectEnvMap) {
|
||||
this.runConfig.envMap = strMapToObj(projectEnvMap);
|
||||
|
@ -341,10 +341,9 @@ export default {
|
|||
this.runConfig.environmentGroupId = id;
|
||||
},
|
||||
getWsProjects() {
|
||||
getOwnerProjects()
|
||||
.then((res) => {
|
||||
this.projectList = res.data;
|
||||
});
|
||||
getOwnerProjects().then((res) => {
|
||||
this.projectList = res.data;
|
||||
});
|
||||
},
|
||||
showPopover() {
|
||||
this.projectIds.clear();
|
||||
|
@ -374,7 +373,7 @@ export default {
|
|||
this.$refs.envPopover.openEnvSelect();
|
||||
});
|
||||
} else if (this.type === "plan") {
|
||||
param = {id: this.planId};
|
||||
param = { id: this.planId };
|
||||
getPlanCaseEnv(param).then((res) => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
|
|
|
@ -3,32 +3,33 @@
|
|||
destroy-on-close
|
||||
:title="$t('load_test.runtime_config')"
|
||||
width="550px"
|
||||
style="margin-top: -8.65vh;max-height: 87.3vh"
|
||||
style="margin-top: -8.65vh; max-height: 87.3vh"
|
||||
@close="close"
|
||||
:visible.sync="runModeVisible"
|
||||
>
|
||||
<div class="env-container">
|
||||
<div>
|
||||
<div>{{ $t("commons.environment") }}:</div>
|
||||
<env-select-popover :project-ids="projectIds"
|
||||
:project-list="projectList"
|
||||
:project-env-map="projectEnvListMap"
|
||||
:environment-type.sync="runConfig.environmentType"
|
||||
:has-option-group="true"
|
||||
:group-id="runConfig.environmentGroupId"
|
||||
@setProjectEnvMap="setProjectEnvMap"
|
||||
@setEnvGroup="setEnvGroup"
|
||||
ref="envSelectPopover"
|
||||
class="mode-row"
|
||||
<env-select-popover
|
||||
:project-ids="projectIds"
|
||||
:project-list="projectList"
|
||||
:project-env-map="projectEnvListMap"
|
||||
:environment-type.sync="runConfig.environmentType"
|
||||
:has-option-group="true"
|
||||
:group-id="runConfig.environmentGroupId"
|
||||
@setProjectEnvMap="setProjectEnvMap"
|
||||
@setEnvGroup="setEnvGroup"
|
||||
ref="envSelectPopover"
|
||||
class="mode-row"
|
||||
></env-select-popover>
|
||||
</div>
|
||||
<div v-if="haveUICase">
|
||||
<div>{{ $t("ui.browser") }}:</div>
|
||||
<div >
|
||||
<div>
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="runConfig.browser"
|
||||
style="width: 100% "
|
||||
style="width: 100%"
|
||||
class="mode-row"
|
||||
>
|
||||
<el-option
|
||||
|
@ -42,7 +43,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<div class="mode-row">{{ $t("run_mode.title") }}:</div>
|
||||
<div >
|
||||
<div>
|
||||
<el-radio-group
|
||||
v-model="runConfig.mode"
|
||||
@change="changeMode"
|
||||
|
@ -54,13 +55,17 @@
|
|||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div >
|
||||
<div>
|
||||
<div class="mode-row">{{ $t("run_mode.other_config") }}:</div>
|
||||
<div >
|
||||
<div>
|
||||
<!-- 串行 -->
|
||||
<div
|
||||
class="mode-row"
|
||||
v-if="runConfig.mode === 'serial' && testType === 'API' && haveOtherExecCase"
|
||||
v-if="
|
||||
runConfig.mode === 'serial' &&
|
||||
testType === 'API' &&
|
||||
haveOtherExecCase
|
||||
"
|
||||
>
|
||||
<el-checkbox
|
||||
v-model="runConfig.runWithinResourcePool"
|
||||
|
@ -68,13 +73,13 @@
|
|||
class="radio-change"
|
||||
:disabled="runMode === 'POOL'"
|
||||
>
|
||||
{{ $t("run_mode.run_with_resource_pool") }}
|
||||
</el-checkbox><br/>
|
||||
{{ $t("run_mode.run_with_resource_pool") }} </el-checkbox
|
||||
><br />
|
||||
<el-select
|
||||
:disabled="!runConfig.runWithinResourcePool"
|
||||
v-model="runConfig.resourcePoolId"
|
||||
size="mini"
|
||||
style="width:100%; margin-top: 8px"
|
||||
style="width: 100%; margin-top: 8px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in resourcePools"
|
||||
|
@ -88,7 +93,11 @@
|
|||
<!-- 并行 -->
|
||||
<div
|
||||
class="mode-row"
|
||||
v-if="runConfig.mode === 'parallel' && testType === 'API' && haveOtherExecCase"
|
||||
v-if="
|
||||
runConfig.mode === 'parallel' &&
|
||||
testType === 'API' &&
|
||||
haveOtherExecCase
|
||||
"
|
||||
>
|
||||
<el-checkbox
|
||||
v-model="runConfig.runWithinResourcePool"
|
||||
|
@ -96,13 +105,13 @@
|
|||
class="radio-change"
|
||||
:disabled="runMode === 'POOL'"
|
||||
>
|
||||
{{ $t("run_mode.run_with_resource_pool") }}
|
||||
</el-checkbox><br/>
|
||||
{{ $t("run_mode.run_with_resource_pool") }} </el-checkbox
|
||||
><br />
|
||||
<el-select
|
||||
:disabled="!runConfig.runWithinResourcePool"
|
||||
v-model="runConfig.resourcePoolId"
|
||||
size="mini"
|
||||
style="width:100%; margin-top: 8px"
|
||||
style="width: 100%; margin-top: 8px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in resourcePools"
|
||||
|
@ -116,7 +125,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 失败重试 -->
|
||||
<div class="mode-row" v-if="isHasLicense">
|
||||
<div class="mode-row">
|
||||
<el-checkbox
|
||||
v-model="runConfig.retryEnable"
|
||||
class="radio-change ms-failure-div-right"
|
||||
|
@ -126,8 +135,11 @@
|
|||
<span v-if="runConfig.retryEnable">
|
||||
<el-tooltip placement="top" style="margin: 0 4px 0 2px">
|
||||
<div slot="content">{{ $t("run_mode.retry_message") }}</div>
|
||||
<i class="el-icon-question" style="cursor: pointer"/>
|
||||
</el-tooltip><br/>
|
||||
<i
|
||||
class="el-icon-question"
|
||||
style="cursor: pointer"
|
||||
/> </el-tooltip
|
||||
><br />
|
||||
<span>
|
||||
{{ $t("run_mode.retry") }}
|
||||
<el-input-number
|
||||
|
@ -136,7 +148,7 @@
|
|||
:min="1"
|
||||
:max="10000000"
|
||||
size="mini"
|
||||
style="width: 103px;margin-top: 8px"
|
||||
style="width: 103px; margin-top: 8px"
|
||||
/>
|
||||
|
||||
{{ $t("run_mode.retry_frequency") }}
|
||||
|
@ -145,14 +157,16 @@
|
|||
</div>
|
||||
|
||||
<div class="mode-row" v-if="runConfig.mode === 'serial'">
|
||||
<el-checkbox v-model="runConfig.onSampleError" class="radio-change">{{
|
||||
$t("api_test.fail_to_stop")
|
||||
}}
|
||||
<el-checkbox v-model="runConfig.onSampleError" class="radio-change"
|
||||
>{{ $t("api_test.fail_to_stop") }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="mode-row" v-if="haveUICase">
|
||||
<el-checkbox v-model="runConfig.headlessEnabled" class="radio-change">
|
||||
<el-checkbox
|
||||
v-model="runConfig.headlessEnabled"
|
||||
class="radio-change"
|
||||
>
|
||||
{{ $t("ui.performance_mode") }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
@ -165,55 +179,64 @@
|
|||
<el-button @click="close">{{ $t("commons.cancel") }}</el-button>
|
||||
<el-dropdown @command="handleCommand" style="margin-left: 5px">
|
||||
<el-button type="primary">
|
||||
{{
|
||||
$t("api_test.run")
|
||||
{{ $t("api_test.run")
|
||||
}}<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="run">{{
|
||||
$t("api_test.run")
|
||||
}}
|
||||
<el-dropdown-item command="run"
|
||||
>{{ $t("api_test.run") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="runAndSave">{{
|
||||
$t("load_test.save_and_run")
|
||||
}}
|
||||
<el-dropdown-item command="runAndSave"
|
||||
>{{ $t("load_test.save_and_run") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="save">{{
|
||||
$t("commons.save")
|
||||
}}
|
||||
<el-dropdown-item command="save"
|
||||
>{{ $t("commons.save") }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch"/>
|
||||
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
|
||||
import {hasLicense} from "metersphere-frontend/src/utils/permission";
|
||||
import {strMapToObj} from "metersphere-frontend/src/utils";
|
||||
import { strMapToObj } from "metersphere-frontend/src/utils";
|
||||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
|
||||
import {getCurrentProjectID, getOwnerProjects} from "@/business/utils/sdk-utils";
|
||||
import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
|
||||
import { ENV_TYPE } from "metersphere-frontend/src/utils/constants";
|
||||
import {
|
||||
getCurrentProjectID,
|
||||
getOwnerProjects,
|
||||
} from "@/business/utils/sdk-utils";
|
||||
import { getQuotaValidResourcePools } from "@/api/remote/resource-pool";
|
||||
import EnvGroupPopover from "@/business/plan/env/EnvGroupPopover";
|
||||
import {getApiCaseEnv} from "@/api/remote/plan/test-plan-api-case";
|
||||
import {getApiScenarioEnv, getPlanCaseEnv, getPlanCaseProjectIds} from "@/api/remote/plan/test-plan";
|
||||
import { getApiCaseEnv } from "@/api/remote/plan/test-plan-api-case";
|
||||
import {
|
||||
getApiScenarioEnv,
|
||||
getPlanCaseEnv,
|
||||
getPlanCaseProjectIds,
|
||||
} from "@/api/remote/plan/test-plan";
|
||||
import EnvGroupWithOption from "../env/EnvGroupWithOption";
|
||||
import EnvironmentGroup from "@/business/plan/env/EnvironmentGroupList";
|
||||
import EnvSelectPopover from "@/business/plan/env/EnvSelectPopover";
|
||||
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
|
||||
import {getProjectConfig} from "@/api/project";
|
||||
import { getSystemBaseSetting } from "metersphere-frontend/src/api/system";
|
||||
import { getProjectConfig } from "@/api/project";
|
||||
|
||||
export default {
|
||||
name: "MsTestPlanRunModeWithEnv",
|
||||
components: {EnvGroupPopover, MsDialogFooter,MsTag,EnvGroupWithOption,EnvironmentGroup,EnvSelectPopover},
|
||||
components: {
|
||||
EnvGroupPopover,
|
||||
MsDialogFooter,
|
||||
MsTag,
|
||||
EnvGroupWithOption,
|
||||
EnvironmentGroup,
|
||||
EnvSelectPopover,
|
||||
},
|
||||
computed: {
|
||||
ENV_TYPE() {
|
||||
return ENV_TYPE;
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -221,7 +244,7 @@ export default {
|
|||
btnStyle: {
|
||||
width: "260px",
|
||||
},
|
||||
result:{loading: false},
|
||||
result: { loading: false },
|
||||
runModeVisible: false,
|
||||
testType: null,
|
||||
resourcePools: [],
|
||||
|
@ -239,7 +262,6 @@ export default {
|
|||
retryNum: 1,
|
||||
browser: "CHROME",
|
||||
},
|
||||
isHasLicense: hasLicense(),
|
||||
projectList: [],
|
||||
projectIds: new Set(),
|
||||
options: [
|
||||
|
@ -290,8 +312,12 @@ export default {
|
|||
if (runModeConfig) {
|
||||
this.runConfig = JSON.parse(runModeConfig);
|
||||
this.runConfig.envMap = new Map();
|
||||
this.runConfig.onSampleError = this.runConfig.onSampleError === 'true' || this.runConfig.onSampleError === true;
|
||||
this.runConfig.runWithinResourcePool = this.runConfig.runWithinResourcePool === 'true' || this.runConfig.runWithinResourcePool === true;
|
||||
this.runConfig.onSampleError =
|
||||
this.runConfig.onSampleError === "true" ||
|
||||
this.runConfig.onSampleError === true;
|
||||
this.runConfig.runWithinResourcePool =
|
||||
this.runConfig.runWithinResourcePool === "true" ||
|
||||
this.runConfig.runWithinResourcePool === true;
|
||||
}
|
||||
this.runModeVisible = true;
|
||||
this.testType = testType;
|
||||
|
@ -302,21 +328,21 @@ export default {
|
|||
},
|
||||
query() {
|
||||
this.loading = true;
|
||||
this.result = getSystemBaseSetting().then(response => {
|
||||
this.result = getSystemBaseSetting().then((response) => {
|
||||
if (!response.data.runMode) {
|
||||
response.data.runMode = 'LOCAL'
|
||||
response.data.runMode = "LOCAL";
|
||||
}
|
||||
this.runMode = response.data.runMode;
|
||||
if (this.runMode === 'POOL') {
|
||||
if (this.runMode === "POOL") {
|
||||
this.runConfig.runWithinResourcePool = true;
|
||||
this.getProjectApplication();
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
getProjectApplication() {
|
||||
getProjectConfig(getCurrentProjectID(), "").then(res => {
|
||||
getProjectConfig(getCurrentProjectID(), "").then((res) => {
|
||||
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
|
||||
this.runConfig.resourcePoolId = res.data.resourcePoolId;
|
||||
}
|
||||
|
@ -346,10 +372,9 @@ export default {
|
|||
this.close();
|
||||
},
|
||||
getResourcePools() {
|
||||
getQuotaValidResourcePools()
|
||||
.then((response) => {
|
||||
this.resourcePools = response.data;
|
||||
});
|
||||
getQuotaValidResourcePools().then((response) => {
|
||||
this.resourcePools = response.data;
|
||||
});
|
||||
},
|
||||
setProjectEnvMap(projectEnvMap) {
|
||||
this.runConfig.envMap = projectEnvMap;
|
||||
|
@ -358,10 +383,9 @@ export default {
|
|||
this.runConfig.environmentGroupId = id;
|
||||
},
|
||||
getWsProjects() {
|
||||
getOwnerProjects()
|
||||
.then((res) => {
|
||||
this.projectList = res.data;
|
||||
});
|
||||
getOwnerProjects().then((res) => {
|
||||
this.projectList = res.data;
|
||||
});
|
||||
},
|
||||
showPopover() {
|
||||
this.projectIds.clear();
|
||||
|
@ -391,7 +415,7 @@ export default {
|
|||
this.$refs.envSelectPopover.open();
|
||||
});
|
||||
} else if (this.type === "plan") {
|
||||
param = {id: this.planId};
|
||||
param = { id: this.planId };
|
||||
getPlanCaseEnv(param).then((res) => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
|
@ -401,7 +425,7 @@ export default {
|
|||
}
|
||||
}
|
||||
if (this.projectIds.size === 0) {
|
||||
param = {id: this.planId};
|
||||
param = { id: this.planId };
|
||||
getPlanCaseProjectIds(param).then((res) => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
|
@ -416,28 +440,27 @@ export default {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
handleCommand(command) {
|
||||
if (
|
||||
this.runConfig.runWithinResourcePool &&
|
||||
this.runConfig.resourcePoolId == null && this.haveOtherExecCase
|
||||
this.runConfig.resourcePoolId == null &&
|
||||
this.haveOtherExecCase
|
||||
) {
|
||||
this.$warning(
|
||||
this.$t("workspace.env_group.please_select_run_within_resource_pool")
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.runConfig.envMap =strMapToObj(this.runConfig.envMap)
|
||||
this.runConfig.envMap = strMapToObj(this.runConfig.envMap);
|
||||
if (command === "runAndSave") {
|
||||
this.runConfig.executionWay = "runAndSave";
|
||||
} else if(command === "save"){
|
||||
} else if (command === "save") {
|
||||
this.runConfig.executionWay = "save";
|
||||
} else {
|
||||
this.runConfig.executionWay = "run";
|
||||
}
|
||||
this.handleRunBatch();
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -461,13 +484,12 @@ export default {
|
|||
.mode-row {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss" scoped>
|
||||
.radio-change:deep(.el-radio__input.is-checked + .el-radio__label) {
|
||||
color: #606266 !important;
|
||||
}
|
||||
.radio-change:deep(.el-checkbox__input.is-checked+.el-checkbox__label) {
|
||||
.radio-change:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
|
||||
color: #606266 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,60 +1,97 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-loading="result.loading"
|
||||
:close-on-click-modal="false" width="60%" class="schedule-edit" :visible.sync="dialogVisible"
|
||||
:append-to-body='true'
|
||||
@close="close">
|
||||
:close-on-click-modal="false"
|
||||
width="60%"
|
||||
class="schedule-edit"
|
||||
:visible.sync="dialogVisible"
|
||||
:append-to-body="true"
|
||||
@close="close"
|
||||
>
|
||||
<template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane :label="$t('schedule.task_config')" name="first">
|
||||
<div class="el-step__icon is-text" style="margin-right: 10px;">
|
||||
<div class="el-step__icon is-text" style="margin-right: 10px">
|
||||
<div class="el-step__icon-inner">1</div>
|
||||
</div>
|
||||
<span>{{ $t('schedule.edit_timer_task') }}</span>
|
||||
<el-form :model="form" :rules="rules" ref="from" style="padding-top: 10px;margin-left: 20px;"
|
||||
class="ms-el-form-item__error">
|
||||
<el-form-item :label="$t('commons.schedule_cron_title')"
|
||||
prop="cronValue" style="height: 50px">
|
||||
<span>{{ $t("schedule.edit_timer_task") }}</span>
|
||||
<el-form
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
ref="from"
|
||||
style="padding-top: 10px; margin-left: 20px"
|
||||
class="ms-el-form-item__error"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$t('commons.schedule_cron_title')"
|
||||
prop="cronValue"
|
||||
style="height: 50px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<el-input :disabled="isReadOnly" v-model="form.cronValue" class="inp"
|
||||
:placeholder="$t('schedule.please_input_cron_expression')" size="mini">
|
||||
<a :disabled="isReadOnly" type="primary" @click="showCronDialog" slot="suffix" class="head">
|
||||
{{ $t('schedule.generate_expression') }}
|
||||
<el-input
|
||||
:disabled="isReadOnly"
|
||||
v-model="form.cronValue"
|
||||
class="inp"
|
||||
:placeholder="$t('schedule.please_input_cron_expression')"
|
||||
size="mini"
|
||||
>
|
||||
<a
|
||||
:disabled="isReadOnly"
|
||||
type="primary"
|
||||
@click="showCronDialog"
|
||||
slot="suffix"
|
||||
class="head"
|
||||
>
|
||||
{{ $t("schedule.generate_expression") }}
|
||||
</a>
|
||||
</el-input>
|
||||
|
||||
<span>{{ this.$t('commons.schedule_switch') }}</span>
|
||||
<el-tooltip effect="dark" placement="bottom"
|
||||
:content="schedule.enable ? $t('commons.close_schedule') : $t('commons.open_schedule')">
|
||||
<el-switch v-model="schedule.enable" style="margin-left: 20px"></el-switch>
|
||||
<span>{{ this.$t("commons.schedule_switch") }}</span>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
placement="bottom"
|
||||
:content="
|
||||
schedule.enable
|
||||
? $t('commons.close_schedule')
|
||||
: $t('commons.open_schedule')
|
||||
"
|
||||
>
|
||||
<el-switch
|
||||
v-model="schedule.enable"
|
||||
style="margin-left: 20px"
|
||||
></el-switch>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-button :disabled="isReadOnly" type="primary" @click="saveCron" size="mini">{{
|
||||
$t('commons.save')
|
||||
}}
|
||||
<el-button
|
||||
:disabled="isReadOnly"
|
||||
type="primary"
|
||||
@click="saveCron"
|
||||
size="mini"
|
||||
>{{ $t("commons.save") }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-form-item>
|
||||
<crontab-result :ex="form.cronValue" ref="crontabResult"/>
|
||||
<crontab-result :ex="form.cronValue" ref="crontabResult" />
|
||||
</el-form>
|
||||
|
||||
<div class="el-step__icon is-text" style="margin-right: 10px;">
|
||||
<div class="el-step__icon is-text" style="margin-right: 10px">
|
||||
<div class="el-step__icon-inner">2</div>
|
||||
</div>
|
||||
<span>{{ $t('load_test.runtime_config') }}</span>
|
||||
<span>{{ $t("load_test.runtime_config") }}</span>
|
||||
<div class="ms-mode-div">
|
||||
<span class="ms-mode-span">{{ $t("run_mode.title") }}:</span>
|
||||
<el-radio-group v-model="runConfig.mode" @change="changeMode">
|
||||
<el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio>
|
||||
<el-radio label="parallel">{{ $t("run_mode.parallel") }}</el-radio>
|
||||
<el-radio label="parallel">{{
|
||||
$t("run_mode.parallel")
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div style="margin-top: 10px;" v-if="haveUICase">
|
||||
<div style="margin-top: 10px" v-if="haveUICase">
|
||||
<span class="ms-mode-span">{{ $t("浏览器") }}:</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
|
@ -72,20 +109,30 @@
|
|||
<div class="ms-mode-div" v-if="runConfig.mode === 'serial'">
|
||||
<el-row>
|
||||
<el-col :span="3">
|
||||
<span class="ms-mode-span">{{ $t("run_mode.other_config") }}:</span>
|
||||
<span class="ms-mode-span"
|
||||
>{{ $t("run_mode.other_config") }}:</span
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div v-if="testType === 'API'">
|
||||
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;" :disabled="runMode === 'POOL'">
|
||||
{{ $t('run_mode.run_with_resource_pool') }}
|
||||
<el-checkbox
|
||||
v-model="runConfig.runWithinResourcePool"
|
||||
style="padding-right: 10px"
|
||||
:disabled="runMode === 'POOL'"
|
||||
>
|
||||
{{ $t("run_mode.run_with_resource_pool") }}
|
||||
</el-checkbox>
|
||||
<el-select :disabled="!runConfig.runWithinResourcePool" v-model="runConfig.resourcePoolId"
|
||||
size="mini">
|
||||
<el-select
|
||||
:disabled="!runConfig.runWithinResourcePool"
|
||||
v-model="runConfig.resourcePoolId"
|
||||
size="mini"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in resourcePools"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id">
|
||||
:value="item.id"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
@ -95,21 +142,31 @@
|
|||
<div class="ms-mode-div" v-if="runConfig.mode === 'parallel'">
|
||||
<el-row>
|
||||
<el-col :span="3">
|
||||
<span class="ms-mode-span">{{ $t("run_mode.other_config") }}:</span>
|
||||
<span class="ms-mode-span"
|
||||
>{{ $t("run_mode.other_config") }}:</span
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div v-if="testType === 'API'">
|
||||
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;" :disabled="runMode === 'POOL'">
|
||||
{{ $t('run_mode.run_with_resource_pool') }}
|
||||
<el-checkbox
|
||||
v-model="runConfig.runWithinResourcePool"
|
||||
style="padding-right: 10px"
|
||||
:disabled="runMode === 'POOL'"
|
||||
>
|
||||
{{ $t("run_mode.run_with_resource_pool") }}
|
||||
</el-checkbox>
|
||||
<el-select :disabled="!runConfig.runWithinResourcePool" v-model="runConfig.resourcePoolId"
|
||||
size="mini">
|
||||
<el-select
|
||||
:disabled="!runConfig.runWithinResourcePool"
|
||||
v-model="runConfig.resourcePoolId"
|
||||
size="mini"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in resourcePools"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:disabled="!item.api"
|
||||
:value="item.id">
|
||||
:value="item.id"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
@ -118,45 +175,55 @@
|
|||
</div>
|
||||
|
||||
<!-- 失败重试 -->
|
||||
<div class="ms-mode-div" v-if="isHasLicense">
|
||||
<div class="ms-mode-div">
|
||||
<el-row>
|
||||
<el-col :span="3">
|
||||
<span class="ms-mode-span"> </span>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<el-checkbox v-model="runConfig.retryEnable" class="ms-failure-div-right">
|
||||
{{ $t('run_mode.retry_on_failure') }}
|
||||
<el-checkbox
|
||||
v-model="runConfig.retryEnable"
|
||||
class="ms-failure-div-right"
|
||||
>
|
||||
{{ $t("run_mode.retry_on_failure") }}
|
||||
</el-checkbox>
|
||||
<span v-if="runConfig.retryEnable">
|
||||
<el-tooltip placement="top">
|
||||
<div slot="content">{{ $t('run_mode.retry_message') }}</div>
|
||||
<i class="el-icon-question" style="cursor: pointer"/>
|
||||
</el-tooltip>
|
||||
<span>
|
||||
{{ $t('run_mode.retry') }}
|
||||
<el-input-number :value="runConfig.retryNum" v-model="runConfig.retryNum" :min="1" :max="10000000"
|
||||
size="mini"/>
|
||||
|
||||
{{ $t('run_mode.retry_frequency') }}
|
||||
</span>
|
||||
<el-tooltip placement="top">
|
||||
<div slot="content">
|
||||
{{ $t("run_mode.retry_message") }}
|
||||
</div>
|
||||
<i class="el-icon-question" style="cursor: pointer" />
|
||||
</el-tooltip>
|
||||
<span>
|
||||
{{ $t("run_mode.retry") }}
|
||||
<el-input-number
|
||||
:value="runConfig.retryNum"
|
||||
v-model="runConfig.retryNum"
|
||||
:min="1"
|
||||
:max="10000000"
|
||||
size="mini"
|
||||
/>
|
||||
|
||||
{{ $t("run_mode.retry_frequency") }}
|
||||
</span>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="ms-failure-div" v-if="runConfig.mode === 'serial'" >
|
||||
<div class="ms-failure-div" v-if="runConfig.mode === 'serial'">
|
||||
<el-row>
|
||||
<el-col :span="18" :offset="3">
|
||||
<div>
|
||||
<el-checkbox v-model="runConfig.onSampleError">{{ $t("api_test.fail_to_stop") }}</el-checkbox>
|
||||
<el-checkbox v-model="runConfig.onSampleError">{{
|
||||
$t("api_test.fail_to_stop")
|
||||
}}</el-checkbox>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div v-if="haveUICase">
|
||||
<el-row>
|
||||
<el-col :span="3">
|
||||
|
||||
</el-col>
|
||||
<el-col :span="3"> </el-col>
|
||||
<el-col :span="18">
|
||||
<div style="margin-top: 10px">
|
||||
<el-checkbox v-model="runConfig.headlessEnabled">
|
||||
|
@ -167,15 +234,25 @@
|
|||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-dialog width="60%" :title="$t('schedule.generate_expression')" :visible.sync="showCron"
|
||||
:modal="false">
|
||||
<crontab @hide="showCron=false" @fill="crontabFill" :expression="schedule.value"
|
||||
ref="crontab"/>
|
||||
<el-dialog
|
||||
width="60%"
|
||||
:title="$t('schedule.generate_expression')"
|
||||
:visible.sync="showCron"
|
||||
:modal="false"
|
||||
>
|
||||
<crontab
|
||||
@hide="showCron = false"
|
||||
@fill="crontabFill"
|
||||
:expression="schedule.value"
|
||||
ref="crontab"
|
||||
/>
|
||||
</el-dialog>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('schedule.task_notification')" name="second">
|
||||
<ms-schedule-notification :test-id="testId"
|
||||
:schedule-receiver-options="scheduleReceiverOptions"/>
|
||||
<ms-schedule-notification
|
||||
:test-id="testId"
|
||||
:schedule-receiver-options="scheduleReceiverOptions"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
@ -184,30 +261,36 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {getCurrentProjectID, getCurrentUser, getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
|
||||
import {hasLicense} from "metersphere-frontend/src/utils/permission";
|
||||
import {listenGoBack, removeGoBackListener} from "metersphere-frontend/src/utils";
|
||||
import {
|
||||
getCurrentProjectID,
|
||||
getCurrentUser,
|
||||
getCurrentWorkspaceId,
|
||||
} from "metersphere-frontend/src/utils/token";
|
||||
import {
|
||||
listenGoBack,
|
||||
removeGoBackListener,
|
||||
} from "metersphere-frontend/src/utils";
|
||||
import Crontab from "metersphere-frontend/src/components/cron/Crontab";
|
||||
import CrontabResult from "metersphere-frontend/src/components/cron/CrontabResult";
|
||||
import {cronValidate} from "metersphere-frontend/src/utils/cron";
|
||||
import { cronValidate } from "metersphere-frontend/src/utils/cron";
|
||||
import MsScheduleNotification from "./ScheduleNotification";
|
||||
import ScheduleSwitch from "./ScheduleSwitch";
|
||||
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
|
||||
import { ENV_TYPE } from "metersphere-frontend/src/utils/constants";
|
||||
import MxNotification from "metersphere-frontend/src/components/MxNoticeTemplate";
|
||||
import {
|
||||
createSchedule,
|
||||
getPlanSchedule,
|
||||
updateSchedule,
|
||||
updateScheduleEnableByPrimyKey
|
||||
updateScheduleEnableByPrimyKey,
|
||||
} from "@/api/remote/plan/test-plan";
|
||||
import {saveNotice} from "@/api/notice";
|
||||
import {getProjectMember} from "@/api/user";
|
||||
import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
|
||||
import {getProjectConfig} from "@/api/project";
|
||||
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
|
||||
import { saveNotice } from "@/api/notice";
|
||||
import { getProjectMember } from "@/api/user";
|
||||
import { getQuotaValidResourcePools } from "@/api/remote/resource-pool";
|
||||
import { getProjectConfig } from "@/api/project";
|
||||
import { getSystemBaseSetting } from "metersphere-frontend/src/api/system";
|
||||
|
||||
function defaultCustomValidate() {
|
||||
return {pass: true};
|
||||
return { pass: true };
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -217,47 +300,46 @@ export default {
|
|||
ScheduleSwitch,
|
||||
Crontab,
|
||||
MsScheduleNotification,
|
||||
"NoticeTemplate": MxNotification,
|
||||
NoticeTemplate: MxNotification,
|
||||
},
|
||||
|
||||
props: {
|
||||
customValidate: {
|
||||
type: Function,
|
||||
default: defaultCustomValidate
|
||||
default: defaultCustomValidate,
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
planCaseIds: [],
|
||||
type: String,
|
||||
//是否含有ui场景 有 ui 场景就要展示 浏览器选项,性能模式
|
||||
haveUICase: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
'schedule.value'() {
|
||||
"schedule.value"() {
|
||||
this.form.cronValue = this.schedule.value;
|
||||
},
|
||||
'runConfig.runWithinResourcePool'() {
|
||||
"runConfig.runWithinResourcePool"() {
|
||||
if (!this.runConfig.runWithinResourcePool) {
|
||||
this.runConfig.resourcePoolId = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const validateCron = (rule, cronValue, callback) => {
|
||||
let customValidate = this.customValidate(this.getIntervalTime());
|
||||
if (!cronValue) {
|
||||
callback(new Error(this.$t('commons.input_content')));
|
||||
callback(new Error(this.$t("commons.input_content")));
|
||||
} else if (!cronValidate(cronValue)) {
|
||||
callback(new Error(this.$t('schedule.cron_expression_format_error')));
|
||||
callback(new Error(this.$t("schedule.cron_expression_format_error")));
|
||||
} else if (!this.intervalValidate()) {
|
||||
callback(new Error(this.$t('schedule.cron_expression_interval_error')));
|
||||
callback(new Error(this.$t("schedule.cron_expression_interval_error")));
|
||||
} else if (!customValidate.pass) {
|
||||
callback(new Error(customValidate.info));
|
||||
} else {
|
||||
|
@ -269,7 +351,6 @@ export default {
|
|||
};
|
||||
return {
|
||||
runMode: "",
|
||||
isHasLicense: hasLicense(),
|
||||
result: {},
|
||||
scheduleReceiverOptions: [],
|
||||
operation: true,
|
||||
|
@ -281,12 +362,14 @@ export default {
|
|||
testId: String,
|
||||
showCron: false,
|
||||
form: {
|
||||
cronValue: ""
|
||||
cronValue: "",
|
||||
},
|
||||
paramRow: {},
|
||||
activeName: 'first',
|
||||
activeName: "first",
|
||||
rules: {
|
||||
cronValue: [{required: true, validator: validateCron, trigger: 'blur'}],
|
||||
cronValue: [
|
||||
{ required: true, validator: validateCron, trigger: "blur" },
|
||||
],
|
||||
},
|
||||
resourcePools: [],
|
||||
runConfig: {
|
||||
|
@ -298,10 +381,10 @@ export default {
|
|||
retryEnable: false,
|
||||
retryNum: 1,
|
||||
browser: "CHROME",
|
||||
headlessEnabled: true
|
||||
headlessEnabled: true,
|
||||
},
|
||||
projectList: [],
|
||||
testType: 'API',
|
||||
testType: "API",
|
||||
planId: String,
|
||||
projectIds: new Set(),
|
||||
browsers: [
|
||||
|
@ -312,28 +395,28 @@ export default {
|
|||
{
|
||||
label: this.$t("firefox"),
|
||||
value: "FIREFOX",
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
query() {
|
||||
this.loading = true;
|
||||
this.result = getSystemBaseSetting().then(response => {
|
||||
this.result = getSystemBaseSetting().then((response) => {
|
||||
if (!response.data.runMode) {
|
||||
response.data.runMode = 'LOCAL'
|
||||
response.data.runMode = "LOCAL";
|
||||
}
|
||||
this.runMode = response.data.runMode;
|
||||
if (this.runMode === 'POOL') {
|
||||
if (this.runMode === "POOL") {
|
||||
this.runConfig.runWithinResourcePool = true;
|
||||
this.getProjectApplication();
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
getProjectApplication() {
|
||||
getProjectConfig(getCurrentProjectID(), "").then(res => {
|
||||
getProjectConfig(getCurrentProjectID(), "").then((res) => {
|
||||
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
|
||||
this.runConfig.resourcePoolId = res.data.resourcePoolId;
|
||||
}
|
||||
|
@ -350,14 +433,14 @@ export default {
|
|||
return true;
|
||||
},
|
||||
updateTask(param) {
|
||||
this.result = updateScheduleEnableByPrimyKey(param).then(response => {
|
||||
this.taskID = this.paramRow.id;
|
||||
this.result = updateScheduleEnableByPrimyKey(param).then((response) => {
|
||||
this.taskID = this.paramRow.id;
|
||||
this.findSchedule(this.taskID);
|
||||
this.$emit("refreshTable");
|
||||
});
|
||||
},
|
||||
initUserList() {
|
||||
this.result = getProjectMember().then(response => {
|
||||
this.result = getProjectMember().then((response) => {
|
||||
this.scheduleReceiverOptions = response.data;
|
||||
});
|
||||
},
|
||||
|
@ -379,7 +462,7 @@ export default {
|
|||
this.dialogVisible = true;
|
||||
this.form.cronValue = this.schedule.value;
|
||||
listenGoBack(this.close);
|
||||
this.activeName = 'first';
|
||||
this.activeName = "first";
|
||||
this.getResourcePools();
|
||||
this.runConfig.environmentType = ENV_TYPE.JSON;
|
||||
this.runConfig.retryEnable = false;
|
||||
|
@ -387,22 +470,24 @@ export default {
|
|||
},
|
||||
findSchedule() {
|
||||
let scheduleResourceID = this.testId;
|
||||
this.result = getPlanSchedule(scheduleResourceID,"TEST_PLAN_TEST").then(response => {
|
||||
if (response.data != null) {
|
||||
this.schedule = response.data;
|
||||
if (response.data.config) {
|
||||
this.runConfig = JSON.parse(response.data.config);
|
||||
if (this.runConfig.environmentType) {
|
||||
delete this.runConfig.environmentType;
|
||||
this.result = getPlanSchedule(scheduleResourceID, "TEST_PLAN_TEST").then(
|
||||
(response) => {
|
||||
if (response.data != null) {
|
||||
this.schedule = response.data;
|
||||
if (response.data.config) {
|
||||
this.runConfig = JSON.parse(response.data.config);
|
||||
if (this.runConfig.environmentType) {
|
||||
delete this.runConfig.environmentType;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.schedule = {
|
||||
value: "",
|
||||
enable: false,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this.schedule = {
|
||||
value: '',
|
||||
enable: false
|
||||
};
|
||||
}
|
||||
});
|
||||
);
|
||||
},
|
||||
crontabFill(value, resultList) {
|
||||
//确定后回传的值
|
||||
|
@ -412,22 +497,27 @@ export default {
|
|||
this.schedule.enable = true;
|
||||
}
|
||||
this.$refs.crontabResult.resultList = resultList;
|
||||
this.$refs['from'].validate();
|
||||
this.$refs["from"].validate();
|
||||
},
|
||||
showCronDialog() {
|
||||
let tmp = this.schedule.value;
|
||||
this.schedule.value = '';
|
||||
this.schedule.value = "";
|
||||
this.$nextTick(() => {
|
||||
this.schedule.value = tmp;
|
||||
this.showCron = true;
|
||||
});
|
||||
},
|
||||
saveCron() {
|
||||
if (this.runConfig.runWithinResourcePool && this.runConfig.resourcePoolId == null) {
|
||||
this.$warning(this.$t('workspace.env_group.please_select_run_within_resource_pool'));
|
||||
if (
|
||||
this.runConfig.runWithinResourcePool &&
|
||||
this.runConfig.resourcePoolId == null
|
||||
) {
|
||||
this.$warning(
|
||||
this.$t("workspace.env_group.please_select_run_within_resource_pool")
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.$refs['from'].validate((valid) => {
|
||||
this.$refs["from"].validate((valid) => {
|
||||
if (valid) {
|
||||
this.intervalShortValidate();
|
||||
let formCronValue = this.form.cronValue;
|
||||
|
@ -452,8 +542,13 @@ export default {
|
|||
if (!param.workspaceId) {
|
||||
param.workspaceId = getCurrentWorkspaceId();
|
||||
}
|
||||
if (this.runConfig.runWithinResourcePool && this.runConfig.resourcePoolId == null) {
|
||||
this.$warning(this.$t('workspace.env_group.please_select_run_within_resource_pool'));
|
||||
if (
|
||||
this.runConfig.runWithinResourcePool &&
|
||||
this.runConfig.resourcePoolId == null
|
||||
) {
|
||||
this.$warning(
|
||||
this.$t("workspace.env_group.please_select_run_within_resource_pool")
|
||||
);
|
||||
return;
|
||||
}
|
||||
param.config = JSON.stringify(this.runConfig);
|
||||
|
@ -462,12 +557,12 @@ export default {
|
|||
//测试计划页面跳转的创建
|
||||
if (param.id) {
|
||||
updateSchedule(param).then(() => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$success(this.$t("commons.save_success"));
|
||||
this.$emit("refreshTable");
|
||||
});
|
||||
} else {
|
||||
createSchedule(param).then(() => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$success(this.$t("commons.save_success"));
|
||||
this.$emit("refreshTable");
|
||||
});
|
||||
}
|
||||
|
@ -475,7 +570,7 @@ export default {
|
|||
},
|
||||
checkScheduleEdit() {
|
||||
if (this.create) {
|
||||
this.$message(this.$t('api_test.environment.please_save_test'));
|
||||
this.$message(this.$t("api_test.environment.please_save_test"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -483,13 +578,13 @@ export default {
|
|||
saveNotice() {
|
||||
let param = this.buildParam();
|
||||
saveNotice(param).then(() => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$success(this.$t("commons.save_success"));
|
||||
});
|
||||
},
|
||||
close() {
|
||||
this.dialogVisible = false;
|
||||
this.form.cronValue = '';
|
||||
this.$refs['from'].resetFields();
|
||||
this.form.cronValue = "";
|
||||
this.$refs["from"].resetFields();
|
||||
if (!this.schedule.value) {
|
||||
this.$refs.crontabResult.resultList = [];
|
||||
}
|
||||
|
@ -498,12 +593,12 @@ export default {
|
|||
intervalShortValidate() {
|
||||
if (this.schedule.enable && this.getIntervalTime() < 3 * 60 * 1000) {
|
||||
// return false;
|
||||
this.$info(this.$t('schedule.cron_expression_interval_short_error'));
|
||||
this.$info(this.$t("schedule.cron_expression_interval_short_error"));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
resultListChange() {
|
||||
this.$refs['from'].validate();
|
||||
this.$refs["from"].validate();
|
||||
},
|
||||
getIntervalTime() {
|
||||
let resultList = this.$refs.crontabResult.resultList;
|
||||
|
@ -515,7 +610,7 @@ export default {
|
|||
alert(executeTileArr);
|
||||
},
|
||||
getResourcePools() {
|
||||
this.result = getQuotaValidResourcePools().then(response => {
|
||||
this.result = getQuotaValidResourcePools().then((response) => {
|
||||
this.resourcePools = response.data;
|
||||
});
|
||||
},
|
||||
|
@ -528,13 +623,12 @@ export default {
|
|||
computed: {
|
||||
isTesterPermission() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.inp {
|
||||
width: 40%;
|
||||
margin-right: 20px;
|
||||
|
@ -560,7 +654,8 @@ export default {
|
|||
.head {
|
||||
border-bottom: 1px solid var(--primary_color);
|
||||
color: var(--primary_color);
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
||||
Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 失败重试 -->
|
||||
<div class="mode-row" v-if="isHasLicense">
|
||||
<div class="mode-row">
|
||||
<el-checkbox
|
||||
v-model="runConfig.retryEnable"
|
||||
class="ms-failure-div-right"
|
||||
|
@ -127,7 +127,7 @@
|
|||
<span v-if="runConfig.retryEnable">
|
||||
<el-tooltip placement="top" style="margin: 0 4px 0 2px">
|
||||
<div slot="content">{{ $t("run_mode.retry_message") }}</div>
|
||||
<i class="el-icon-question" style="cursor: pointer"/>
|
||||
<i class="el-icon-question" style="cursor: pointer" />
|
||||
</el-tooltip>
|
||||
<span style="margin-left: 10px">
|
||||
{{ $t("run_mode.retry") }}
|
||||
|
@ -146,9 +146,8 @@
|
|||
</div>
|
||||
|
||||
<div class="mode-row" v-if="runConfig.mode === 'serial'">
|
||||
<el-checkbox v-model="runConfig.onSampleError">{{
|
||||
$t("api_test.fail_to_stop")
|
||||
}}
|
||||
<el-checkbox v-model="runConfig.onSampleError"
|
||||
>{{ $t("api_test.fail_to_stop") }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
|
@ -166,38 +165,38 @@
|
|||
<el-button @click="close">{{ $t("commons.cancel") }}</el-button>
|
||||
<el-dropdown @command="handleCommand" style="margin-left: 5px">
|
||||
<el-button type="primary">
|
||||
{{
|
||||
$t("load_test.save_and_run")
|
||||
{{ $t("load_test.save_and_run")
|
||||
}}<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="run">{{
|
||||
$t("load_test.save_and_run")
|
||||
}}
|
||||
<el-dropdown-item command="run"
|
||||
>{{ $t("load_test.save_and_run") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="save">{{
|
||||
$t("commons.save")
|
||||
}}
|
||||
<el-dropdown-item command="save"
|
||||
>{{ $t("commons.save") }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch"/>
|
||||
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getCurrentProjectID, getOwnerProjects, hasLicense} from "@/business/utils/sdk-utils";
|
||||
import {BODY_TYPE as ENV_TYPE} from "@/business/plan/env/ApiTestModel";
|
||||
import {
|
||||
getCurrentProjectID,
|
||||
getOwnerProjects,
|
||||
} from "@/business/utils/sdk-utils";
|
||||
import { BODY_TYPE as ENV_TYPE } from "@/business/plan/env/ApiTestModel";
|
||||
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
|
||||
import EnvPopover from "@/business/plan/env/EnvPopover";
|
||||
import {getProjectConfig} from "@/api/project";
|
||||
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
|
||||
import { getProjectConfig } from "@/api/project";
|
||||
import { getSystemBaseSetting } from "metersphere-frontend/src/api/system";
|
||||
|
||||
export default {
|
||||
name: "MsPlanRunModeWithEnv",
|
||||
components: {EnvPopover, MsDialogFooter},
|
||||
components: { EnvPopover, MsDialogFooter },
|
||||
data() {
|
||||
return {
|
||||
runMode: "",
|
||||
|
@ -220,7 +219,6 @@ export default {
|
|||
retryNum: 1,
|
||||
browser: "CHROME",
|
||||
},
|
||||
isHasLicense: hasLicense(),
|
||||
projectEnvListMap: {},
|
||||
projectList: [],
|
||||
projectIds: new Set(),
|
||||
|
@ -267,8 +265,12 @@ export default {
|
|||
open(testType, runModeConfig) {
|
||||
if (runModeConfig) {
|
||||
this.runConfig = JSON.parse(runModeConfig);
|
||||
this.runConfig.onSampleError = this.runConfig.onSampleError === 'true' || this.runConfig.onSampleError === true;
|
||||
this.runConfig.runWithinResourcePool = this.runConfig.runWithinResourcePool === 'true' || this.runConfig.runWithinResourcePool === true;
|
||||
this.runConfig.onSampleError =
|
||||
this.runConfig.onSampleError === "true" ||
|
||||
this.runConfig.onSampleError === true;
|
||||
this.runConfig.runWithinResourcePool =
|
||||
this.runConfig.runWithinResourcePool === "true" ||
|
||||
this.runConfig.runWithinResourcePool === true;
|
||||
}
|
||||
this.runModeVisible = true;
|
||||
this.testType = testType;
|
||||
|
@ -278,21 +280,21 @@ export default {
|
|||
},
|
||||
query() {
|
||||
this.loading = true;
|
||||
this.result = getSystemBaseSetting().then(response => {
|
||||
this.result = getSystemBaseSetting().then((response) => {
|
||||
if (!response.data.runMode) {
|
||||
response.data.runMode = 'LOCAL'
|
||||
response.data.runMode = "LOCAL";
|
||||
}
|
||||
this.runMode = response.data.runMode;
|
||||
if (this.runMode === 'POOL') {
|
||||
if (this.runMode === "POOL") {
|
||||
this.runConfig.runWithinResourcePool = true;
|
||||
this.getProjectApplication();
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
getProjectApplication() {
|
||||
getProjectConfig(getCurrentProjectID(), "").then(res => {
|
||||
getProjectConfig(getCurrentProjectID(), "").then((res) => {
|
||||
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
|
||||
this.runConfig.resourcePoolId = res.data.resourcePoolId;
|
||||
}
|
||||
|
@ -336,10 +338,9 @@ export default {
|
|||
this.runConfig.environmentGroupId = id;
|
||||
},
|
||||
getWsProjects() {
|
||||
getOwnerProjects()
|
||||
.then((res) => {
|
||||
this.projectList = res.data;
|
||||
});
|
||||
getOwnerProjects().then((res) => {
|
||||
this.projectList = res.data;
|
||||
});
|
||||
},
|
||||
showPopover() {
|
||||
this.projectIds.clear();
|
||||
|
@ -353,7 +354,7 @@ export default {
|
|||
param = this.planCaseIds;
|
||||
} else if (this.type === "plan") {
|
||||
url = "/test/plan/case/env";
|
||||
param = {id: this.planId};
|
||||
param = { id: this.planId };
|
||||
}
|
||||
this.$post(url, param, (res) => {
|
||||
let data = res.data;
|
||||
|
|
|
@ -62,7 +62,6 @@ import ApiResult from "@/business/plan/view/comonents/report/detail/component/Ap
|
|||
import TestPlanReportContainer from "@/business/plan/view/comonents/report/detail/TestPlanReportContainer";
|
||||
import ApiCases from "@/business/plan/view/comonents/report/detail/component/ApiCases";
|
||||
import TabPaneCount from "@/business/plan/view/comonents/report/detail/component/TabPaneCount";
|
||||
import {hasLicense} from "metersphere-frontend/src/utils/permission";
|
||||
import {apiTestExecRerun} from "@/api/remote/ui/api-test";
|
||||
|
||||
export default {
|
||||
|
@ -79,7 +78,7 @@ export default {
|
|||
};
|
||||
},
|
||||
created() {
|
||||
this.showRerunBtn = !this.isShare && hasLicense();
|
||||
this.showRerunBtn = !this.isShare;
|
||||
},
|
||||
props: [
|
||||
'report', 'planId', 'isTemplate', 'isShare', 'shareId', 'isDb'
|
||||
|
|
|
@ -215,7 +215,7 @@ export default {
|
|||
}
|
||||
},
|
||||
rerunVerify() {
|
||||
if (hasLicense() && this.fullTreeNodes && this.fullTreeNodes.length > 0 && !this.isShare) {
|
||||
if (this.fullTreeNodes && this.fullTreeNodes.length > 0 && !this.isShare) {
|
||||
this.fullTreeNodes.forEach(item => {
|
||||
item.redirect = true;
|
||||
if (item.totalStatus === 'FAIL' || item.totalStatus === 'ERROR' || item.unExecuteTotal > 0
|
||||
|
|
Loading…
Reference in New Issue