Conflicts:
	backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java
This commit is contained in:
wenyann 2021-01-26 10:27:57 +08:00
commit b3808d8c90
76 changed files with 1506 additions and 804 deletions

View File

@ -7,9 +7,9 @@
> [English](README-EN.md) | 中文
| Developer Wanted |
| 下载持续测试白皮书 |
| ------------------------------------------------------------------------------------------------------------ |
| 我们正在寻找开发者,欢迎加入我们共同打造更好用、更强大的 MeterSphere。联系我们 [metersphere@fit2cloud.com](mailto:metersphere@fit2cloud.com) |
| 《持续测试白皮书 v1.0》由“软件质量报道”公众号和MeterSphere开源项目组共同编写而成。采用贴近企业实践的视角进行组织编写团队结合自身对软件测试发展历程以及持续测试理念的理解并积极听取行业内其他专家理念在此基础上编写而成。全文从持续测试产生的背景和价值、企业如何实践持续测试以及持续测试成熟度模型这三个方面展开分别介绍了持续测试产生的原因、持续测试落地的关键要素和持续测试成熟度模型的构成、评判等方面内容。下载链接 [https://jinshuju.net/f/KqFUhq](https://jinshuju.net/f/KqFUhq) |
MeterSphere 是一站式开源持续测试平台涵盖测试跟踪、接口测试、性能测试、团队协作等功能兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。

View File

@ -212,7 +212,7 @@
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
<version>3.4.14</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
@ -341,6 +341,11 @@
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlcleaner</groupId>
<artifactId>htmlcleaner</artifactId>
<version>2.24</version>
</dependency>
<dependency>
<groupId>org.json</groupId>

View File

@ -21,7 +21,7 @@ public class ApiScenarioRequest {
private String planId;
private boolean recent = false;
private List<OrderRequest> orders;
private List<String> filters;
private Map<String, List<String>> filters;
private Map<String, Object> combine;
private List<String> ids;
private boolean isSelectThisWeedData;

View File

@ -45,7 +45,7 @@ public class RunScenarioRequest {
*/
private boolean isSelectAllDate;
private List<String> filters;
private Map<String, List<String>> filters;
private String name;

View File

@ -4,6 +4,7 @@ import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Getter
@Setter
@ -23,7 +24,7 @@ public class SaveApiPlanRequest {
*/
private boolean isSelectAllDate;
private List<String> filters;
private Map<String, List<String>> filters;
private String name;

View File

@ -5,6 +5,7 @@ import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Setter
@Getter
@ -47,7 +48,7 @@ public class SaveApiScenarioRequest {
private boolean isSelectAllDate;
private List<String> filters;
private Map<String, List<String>> filters;
private List<String> moduleIds;

View File

@ -60,7 +60,7 @@ public class MsScenario extends MsTestElement {
return;
}
config.setStep(this.name);
config.setStepType("SCENARIO");
config.setEnableCookieShare(enableCookieShare);
if (StringUtils.isNotEmpty(environmentId)) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
@ -121,6 +121,14 @@ public class MsScenario extends MsTestElement {
variables.stream().filter(ScenarioVariable::isConstantValid).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
List<ScenarioVariable> variableList = variables.stream().filter(ScenarioVariable::isListValid).collect(Collectors.toList());
variableList.forEach(item -> {
String[] arrays = item.getValue().split(",");
for (int i = 0; i < arrays.length; i++) {
arguments.addArgument(item.getName() + "_" + (i + 1), arrays[i], "=");
}
});
}
if (config != null && config.getConfig() != null && config.getConfig().getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().getCommonConfig().getVariables())) {

View File

@ -17,6 +17,6 @@ public class ParameterConfig {
// 步骤
private String step;
private final String stepType = "STEP_GROUP";
private String stepType;
}

View File

@ -12,7 +12,11 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.control.*;
import org.apache.jmeter.control.ForeachController;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.control.RunTime;
import org.apache.jmeter.control.WhileController;
import org.apache.jmeter.modifiers.CounterConfig;
import org.apache.jmeter.reporters.ResultAction;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
@ -43,8 +47,21 @@ public class MsLoopController extends MsTestElement {
if (!this.isEnable()) {
return;
}
final HashTree groupTree = controller(tree);
if (StringUtils.equals(this.loopType, LoopConstants.WHILE.name()) && this.whileController != null) {
config.setStep("While 循环");
}
if (StringUtils.equals(this.loopType, LoopConstants.FOREACH.name()) && this.forEachController != null) {
config.setStep("ForEach 循环");
}
if (StringUtils.equals(this.loopType, LoopConstants.LOOP_COUNT.name()) && this.countController != null) {
config.setStep("次数循环");
}
config.setStepType("LOOP");
final HashTree groupTree = controller(tree);
// 循环下都增加一个计数器用于结果统计
groupTree.add(addCounterConfig());
// 不打开执行成功后轮询功能则成功后就停止循环
if (StringUtils.equals(this.loopType, LoopConstants.LOOP_COUNT.name()) && this.countController != null && !countController.isProceed()) {
ResultAction resultAction = new ResultAction();
@ -65,6 +82,18 @@ public class MsLoopController extends MsTestElement {
}
}
private CounterConfig addCounterConfig() {
CounterConfig counterConfig = new CounterConfig();
counterConfig.setVarName("LoopCounterConfigXXX");
counterConfig.setName("LoopCounterConfigXXX");
counterConfig.setEnabled(true);
counterConfig.setProperty(TestElement.TEST_CLASS, CounterConfig.class.getName());
counterConfig.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("CounterConfigGui"));
counterConfig.setStart(1L);
counterConfig.setIncrement(1L);
return counterConfig;
}
private LoopController loopController() {
LoopController loopController = new LoopController();
loopController.setEnabled(true);

View File

@ -7,4 +7,6 @@ public class CountController {
private int loops;
private int interval;
private boolean proceed;
private Object requestResult;
}

View File

@ -7,4 +7,6 @@ public class MsForEachController {
private String inputVal;
private String returnVal;
private String interval;
private Object requestResult;
}

View File

@ -8,4 +8,5 @@ public class MsWhileController {
private String operator;
private String value;
private int timeout;
private Object requestResult;
}

View File

@ -40,7 +40,11 @@ public class MsJSR223Processor extends MsTestElement {
processor.setName("JSR223Processor");
}
if (config != null && StringUtils.isNotEmpty(config.getStep())) {
processor.setName(this.getName() + "<->" + config.getStep());
if ("SCENARIO".equals(config.getStepType())) {
processor.setName(this.getName() + "<->" + config.getStep());
} else {
processor.setName(this.getName() + "<->" + config.getStep() + "-" + "${LoopCounterConfigXXX}");
}
}
processor.setProperty(TestElement.TEST_CLASS, JSR223Sampler.class.getName());
processor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));

View File

@ -78,7 +78,11 @@ public class MsDubboSampler extends MsTestElement {
DubboSample sampler = new DubboSample();
sampler.setName(this.getName());
if (config != null && StringUtils.isNotEmpty(config.getStep())) {
sampler.setName(this.getName() + "<->" + config.getStep());
if ("SCENARIO".equals(config.getStepType())) {
sampler.setName(this.getName() + "<->" + config.getStep());
} else {
sampler.setName(this.getName() + "<->" + config.getStep() + "-" + "${LoopCounterConfigXXX}");
}
}
sampler.setProperty(TestElement.TEST_CLASS, DubboSample.class.getName());
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("DubboSampleGui"));

View File

@ -97,7 +97,11 @@ public class MsHTTPSamplerProxy extends MsTestElement {
sampler.setEnabled(true);
sampler.setName(this.getName());
if (config != null && StringUtils.isNotEmpty(config.getStep())) {
sampler.setName(this.getName() + "<->" + config.getStep());
if ("SCENARIO".equals(config.getStepType())) {
sampler.setName(this.getName() + "<->" + config.getStep());
} else {
sampler.setName(this.getName() + "<->" + config.getStep() + "-" + "${LoopCounterConfigXXX}");
}
}
sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());

View File

@ -50,6 +50,8 @@ public class MsJDBCSampler extends MsTestElement {
private Object requestResult;
@JSONField(ordinal = 28)
private String dataSourceId;
@JSONField(ordinal = 29)
private String protocol="SQL";
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
@ -107,7 +109,11 @@ public class MsJDBCSampler extends MsTestElement {
JDBCSampler sampler = new JDBCSampler();
sampler.setName(this.getName());
if (config != null && StringUtils.isNotEmpty(config.getStep())) {
sampler.setName(this.getName() + "<->" + config.getStep());
if ("SCENARIO".equals(config.getStepType())) {
sampler.setName(this.getName() + "<->" + config.getStep());
} else {
sampler.setName(this.getName() + "<->" + config.getStep() + "-" + "${LoopCounterConfigXXX}");
}
}
sampler.setProperty(TestElement.TEST_CLASS, JDBCSampler.class.getName());
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));

View File

@ -65,6 +65,8 @@ public class MsTCPSampler extends MsTestElement {
private String useEnvironment;
@JSONField(ordinal = 37)
private MsJSR223PreProcessor tcpPreProcessor;
@JSONField(ordinal = 38)
private String protocol = "TCP";
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
@ -99,7 +101,11 @@ public class MsTCPSampler extends MsTestElement {
TCPSampler tcpSampler = new TCPSampler();
tcpSampler.setName(this.getName());
if (config != null && StringUtils.isNotEmpty(config.getStep())) {
tcpSampler.setName(this.getName() + "<->" + config.getStep());
if ("SCENARIO".equals(config.getStepType())) {
tcpSampler.setName(this.getName() + "<->" + config.getStep());
} else {
tcpSampler.setName(this.getName() + "<->" + config.getStep() + "-" + "${LoopCounterConfigXXX}");
}
}
tcpSampler.setProperty(TestElement.TEST_CLASS, TCPSampler.class.getName());
@ -128,10 +134,12 @@ public class MsTCPSampler extends MsTestElement {
userParameters.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("UserParametersGui"));
List<StringProperty> names = new ArrayList<>();
List<StringProperty> threadValues = new ArrayList<>();
this.parameters.forEach(item -> {
names.add(new StringProperty(new Integer(new Random().nextInt(1000000)).toString(), item.getName()));
threadValues.add(new StringProperty(new Integer(new Random().nextInt(1000000)).toString(), item.getValue()));
});
if (CollectionUtils.isNotEmpty(this.parameters)) {
this.parameters.forEach(item -> {
names.add(new StringProperty(new Integer(new Random().nextInt(1000000)).toString(), item.getName()));
threadValues.add(new StringProperty(new Integer(new Random().nextInt(1000000)).toString(), item.getValue()));
});
}
userParameters.setNames(new CollectionProperty(UserParameters.NAMES, names));
List<CollectionProperty> collectionPropertyList = new ArrayList<>();
collectionPropertyList.add(new CollectionProperty(new Integer(new Random().nextInt(1000000)).toString(), threadValues));

View File

@ -41,8 +41,7 @@ public class ScenarioVariable {
private String maxNumber;
public boolean isConstantValid() {
if ((StringUtils.equals(this.type, VariableTypeConstants.CONSTANT.name())
|| StringUtils.equals(this.type, VariableTypeConstants.LIST.name())) && StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(value)) {
if (StringUtils.equals(this.type, VariableTypeConstants.CONSTANT.name()) && StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(value)) {
return true;
}
return false;
@ -55,6 +54,13 @@ public class ScenarioVariable {
return false;
}
public boolean isListValid() {
if (StringUtils.equals(this.type, VariableTypeConstants.LIST.name()) && StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(value) && value.indexOf(",") != -1) {
return true;
}
return false;
}
public boolean isCounterValid() {
if (StringUtils.equals(this.type, VariableTypeConstants.COUNTER.name()) && StringUtils.isNotEmpty(name)) {
return true;

View File

@ -126,6 +126,34 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
}
protected ApiDefinitionResult buildApiDefinition(String id, String name, String path, String method,ApiTestImportRequest importRequest) {
protected void addBodyHeader(MsHTTPSamplerProxy request) {
String contentType = "";
if (request.getBody() != null && StringUtils.isNotBlank(request.getBody().getType())) {
switch (request.getBody().getType()) {
case Body.WWW_FROM:
contentType = "application/x-www-form-urlencoded";
break;
case Body.JSON:
contentType = "application/json";
break;
case Body.XML:
contentType = "application/xml";
break;
case Body.BINARY:
contentType = "application/octet-stream";
break;
}
List<KeyValue> headers = request.getHeaders();
if (headers == null) {
headers = new ArrayList<>();
request.setHeaders(headers);
}
addContentType(request.getHeaders(), contentType);
}
}
protected ApiDefinitionResult buildApiDefinition(String id, String name, String path, String method) {
ApiDefinitionResult apiDefinition = new ApiDefinitionResult();
apiDefinition.setName(name);
apiDefinition.setPath(formatPath(path));
@ -172,8 +200,8 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
return request;
}
protected void addContentType(HttpRequest request, String contentType) {
// addHeader(request, "Content-Type", contentType);
protected void addContentType(List<KeyValue> headers, String contentType) {
addHeader(headers, "Content-Type", contentType);
}
protected void addCookie(List<KeyValue> headers, String key, String value) {

View File

@ -74,6 +74,7 @@ public class PostmanParser extends ApiImportAbstractParser {
parseBody(request.getBody(), requestDesc);
request.setArguments(parseKeyValue(url.getQuery()));
request.setHeaders(parseKeyValue(requestDesc.getHeader()));
addBodyHeader(request);
apiDefinition.setRequest(JSON.toJSONString(request));
return apiDefinition;
}

View File

@ -68,6 +68,7 @@ public class Swagger2Parser extends SwaggerAbstractParser {
MsHTTPSamplerProxy request = buildRequest(operation, pathName, method.name());
ApiDefinitionResult apiDefinition = buildApiDefinition(request.getId(), operation, pathName, method.name(),importRequest);
parseParameters(operation, request);
addBodyHeader(request);
apiDefinition.setRequest(JSON.toJSONString(request));
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation, operation.getResponses())));
buildModule(parentNode, apiDefinition, operation.getTags(), importRequest.isSaved());
@ -275,7 +276,6 @@ public class Swagger2Parser extends SwaggerAbstractParser {
} else {
propertyList.add(new JSONObject());
}
jsonObject.put(key, propertyList);
} else {
jsonObject.put(key, new ArrayList<>());

View File

@ -100,6 +100,7 @@ public class Swagger3Parser extends SwaggerAbstractParser {
ApiDefinitionResult apiDefinition = buildApiDefinition(request.getId(), operation, pathName, method,importRequest);
parseParameters(operation, request);
parseRequestBody(operation.getRequestBody(), request.getBody());
addBodyHeader(request);
apiDefinition.setRequest(JSON.toJSONString(request));
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses())));
buildModule(parentNode, apiDefinition, operation.getTags(), importRequest.isSaved());

View File

@ -464,7 +464,7 @@ public class ApiAutomationService {
* @param unSelectIds 未勾选ID_前台没有勾选的ID
* @return
*/
private List<String> getAllScenarioIdsByFontedSelect(List<String> moduleIds, String name, String projectId, List<String> filters, List<String> unSelectIds) {
private List<String> getAllScenarioIdsByFontedSelect(List<String> moduleIds, String name, String projectId, Map<String, List<String>> filters, List<String> unSelectIds) {
ApiScenarioRequest selectRequest = new ApiScenarioRequest();
selectRequest.setModuleIds(moduleIds);
selectRequest.setName(name);

View File

@ -65,6 +65,15 @@ public class HistoricalDataUpgradeService {
}
}
private MsScenario createScenarioByTest(ApiTest test) {
MsScenario scenario = new MsScenario();
scenario.setName(test.getName());
scenario.setReferenced("Upgrade");
scenario.setResourceId(UUID.randomUUID().toString());
scenario.setId(test.getId());
return scenario;
}
private MsScenario createScenario(Scenario oldScenario) {
MsScenario scenario = new MsScenario();
scenario.setOldVariables(oldScenario.getVariables());
@ -147,6 +156,9 @@ public class HistoricalDataUpgradeService {
BeanUtils.copyBean(element, request1);
((MsHTTPSamplerProxy) element).setProtocol(RequestType.HTTP);
((MsHTTPSamplerProxy) element).setArguments(request1.getParameters());
List<KeyValue> keyValues = new LinkedList<>();
keyValues.add(new KeyValue("", ""));
((MsHTTPSamplerProxy) element).setRest(keyValues);
if (StringUtils.isEmpty(element.getName())) {
element.setName(request1.getPath());
}
@ -185,6 +197,9 @@ public class HistoricalDataUpgradeService {
CollectionUtils.isNotEmpty(request.getAssertions().getRegex()) || CollectionUtils.isNotEmpty(request.getAssertions().getXpath2()))) {
String assertions = JSON.toJSONString(request.getAssertions());
MsAssertions msAssertions = JSON.parseObject(assertions, MsAssertions.class);
if (StringUtils.isEmpty(msAssertions.getName())) {
msAssertions.setName("Assertions");
}
msAssertions.setType("Assertions");
msAssertions.setIndex(index + "");
msAssertions.setResourceId(UUID.randomUUID().toString());
@ -196,6 +211,9 @@ public class HistoricalDataUpgradeService {
CollectionUtils.isNotEmpty(request.getExtract().getRegex()) || CollectionUtils.isNotEmpty(request.getExtract().getXpath()))) {
String extractJson = JSON.toJSONString(request.getExtract());
MsExtract extract = JSON.parseObject(extractJson, MsExtract.class);
if (StringUtils.isEmpty(extract.getName())) {
extract.setName("Extract");
}
extract.setType("Extract");
extract.setIndex(index + "");
extract.setHashTree(new LinkedList<>());
@ -206,6 +224,9 @@ public class HistoricalDataUpgradeService {
if (request.getJsr223PreProcessor() != null && StringUtils.isNotEmpty(request.getJsr223PreProcessor().getScript())) {
String preJson = JSON.toJSONString(request.getJsr223PreProcessor());
MsJSR223PreProcessor preProcessor = JSON.parseObject(preJson, MsJSR223PreProcessor.class);
if (StringUtils.isEmpty(preProcessor.getName())) {
preProcessor.setName("JSR223PreProcessor");
}
preProcessor.setType("JSR223PreProcessor");
preProcessor.setIndex(index + "");
preProcessor.setHashTree(new LinkedList<>());
@ -216,6 +237,9 @@ public class HistoricalDataUpgradeService {
if (request.getJsr223PostProcessor() != null && StringUtils.isNotEmpty(request.getJsr223PostProcessor().getScript())) {
String preJson = JSON.toJSONString(request.getJsr223PostProcessor());
MsJSR223PostProcessor preProcessor = JSON.parseObject(preJson, MsJSR223PostProcessor.class);
if (StringUtils.isEmpty(preProcessor.getName())) {
preProcessor.setName("JSR223PostProcessor");
}
preProcessor.setType("JSR223PostProcessor");
preProcessor.setIndex(index + "");
preProcessor.setHashTree(new LinkedList<>());
@ -239,9 +263,9 @@ public class HistoricalDataUpgradeService {
return scenario;
}
private ApiScenarioWithBLOBs checkNameExist(Scenario oldScenario, String projectId, ApiScenarioMapper mapper) {
private ApiScenarioWithBLOBs getScenario(String oldScenarioId, ApiScenarioMapper mapper) {
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andIdEqualTo(oldScenario.getId());
example.createCriteria().andIdEqualTo(oldScenarioId);
List<ApiScenarioWithBLOBs> list = mapper.selectByExampleWithBLOBs(example);
if (list.size() > 0) {
return list.get(0);
@ -321,20 +345,20 @@ public class HistoricalDataUpgradeService {
copyDir(dir, BODY_FILE_DIR);
}
private void createApiScenarioWithBLOBs(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade, Scenario oldScenario, String scenarioDefinition, ApiScenarioMapper mapper, int num) {
if (StringUtils.isEmpty(oldScenario.getName())) {
oldScenario.setName("默认名称-" + DateUtils.getTimeStr(System.currentTimeMillis()));
private void createApiScenarioWithBLOBs(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade, String id, String name, int total, String scenarioDefinition, ApiScenarioMapper mapper, int num) {
if (StringUtils.isEmpty(name)) {
name = "默认名称-" + DateUtils.getTimeStr(System.currentTimeMillis());
}
ApiScenarioWithBLOBs scenario = checkNameExist(oldScenario, saveHistoricalDataUpgrade.getProjectId(), mapper);
ApiScenarioWithBLOBs scenario = getScenario(id, mapper);
if (scenario != null) {
scenario.setName(oldScenario.getName());
scenario.setName(name);
scenario.setProjectId(saveHistoricalDataUpgrade.getProjectId());
scenario.setTags(scenario.getTags());
scenario.setLevel("P0");
scenario.setModulePath(saveHistoricalDataUpgrade.getModulePath());
scenario.setApiScenarioModuleId(saveHistoricalDataUpgrade.getModuleId());
scenario.setPrincipal(Objects.requireNonNull(SessionUtils.getUser()).getId());
scenario.setStepTotal(oldScenario.getRequests().size());
scenario.setStepTotal(total);
scenario.setScenarioDefinition(scenarioDefinition);
scenario.setUpdateTime(System.currentTimeMillis());
scenario.setStatus(ScenarioStatus.Underway.name());
@ -342,15 +366,15 @@ public class HistoricalDataUpgradeService {
mapper.updateByPrimaryKeySelective(scenario);
} else {
scenario = new ApiScenarioWithBLOBs();
scenario.setId(oldScenario.getId());
scenario.setName(oldScenario.getName());
scenario.setId(id);
scenario.setName(name);
scenario.setProjectId(saveHistoricalDataUpgrade.getProjectId());
scenario.setTags(scenario.getTags());
scenario.setLevel("P0");
scenario.setModulePath(saveHistoricalDataUpgrade.getModulePath());
scenario.setApiScenarioModuleId(saveHistoricalDataUpgrade.getModuleId());
scenario.setPrincipal(Objects.requireNonNull(SessionUtils.getUser()).getId());
scenario.setStepTotal(oldScenario.getRequests().size());
scenario.setStepTotal(total);
scenario.setScenarioDefinition(scenarioDefinition);
scenario.setCreateTime(System.currentTimeMillis());
scenario.setUpdateTime(System.currentTimeMillis());
@ -374,7 +398,9 @@ public class HistoricalDataUpgradeService {
for (ApiTest test : blobs) {
// 附件迁移
createBodyFiles(test.getId());
// 把test 生成一个场景旧场景数据变成引用步骤
MsScenario scenarioTest = createScenarioByTest(test);
LinkedList<MsTestElement> listSteps = new LinkedList<>();
List<Scenario> scenarios = JSON.parseArray(test.getScenarioDefinition(), Scenario.class);
if (CollectionUtils.isNotEmpty(scenarios)) {
// 批量处理
@ -382,9 +408,20 @@ public class HistoricalDataUpgradeService {
MsScenario scenario1 = createScenario(scenario);
String scenarioDefinition = JSON.toJSONString(scenario1);
num++;
createApiScenarioWithBLOBs(saveHistoricalDataUpgrade, scenario, scenarioDefinition, mapper, num);
createApiScenarioWithBLOBs(saveHistoricalDataUpgrade, scenario.getId(), scenario.getName(), scenario.getRequests().size(), scenarioDefinition, mapper, num);
MsScenario step = new MsScenario();
step.setId(scenario1.getId());
step.setName(scenario1.getName());
step.setType("scenario");
step.setResourceId(UUID.randomUUID().toString());
step.setReferenced("REF");
listSteps.add(step);
}
}
num++;
scenarioTest.setHashTree(listSteps);
String scenarioDefinition = JSON.toJSONString(scenarioTest);
createApiScenarioWithBLOBs(saveHistoricalDataUpgrade, scenarioTest.getId(), scenarioTest.getName(), listSteps.size(), scenarioDefinition, mapper, num);
}
sqlSession.flushStatements();
return null;

View File

@ -150,9 +150,35 @@
</foreach>
</if>
<if test="request.filters != null and request.filters.size() > 0">
and api_scenario.status in
<foreach collection="request.filters" item="value" separator="," open="(" close=")">
#{value}
<foreach collection="request.filters.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key=='status'">
and api_scenario.status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='user_id'">
and api_scenario.user_id in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='level'">
and api_scenario.level in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='last_result'">
and api_scenario.last_result in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
</choose>
</if>
</foreach>
</if>
<if test="request.executeStatus == 'unExecute'">

View File

@ -11,4 +11,5 @@ public interface ExtTestPlanLoadCaseMapper {
List<String> selectIdsNotInPlan(@Param("projectId") String projectId, @Param("planId") String planId);
List<TestPlanLoadCaseDTO> selectTestPlanLoadCaseList(@Param("request") LoadCaseRequest request);
void updateCaseStatus(@Param("reportId") String reportId, @Param("status") String status);
List<String> getStatusByTestPlanId(@Param("planId") String planId);
}

View File

@ -40,6 +40,29 @@
<if test="request.name != null">
and (lt.name like CONCAT('%', #{request.name},'%') or lt.num like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.filters != null and request.filters.size() > 0">
<foreach collection="request.filters.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key=='status'">
and lt.status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
</choose>
</if>
</foreach>
</if>
</where>
<if test="request.orders != null and request.orders.size() > 0">
order by
<foreach collection="request.orders" separator="," item="order">
tplc.${order.name} ${order.type}
</foreach>
</if>
</select>
<select id="getStatusByTestPlanId" resultType="java.lang.String">
select status from test_plan_load_case tplc where tplc.test_plan_id = #{planId}
</select>
</mapper>

View File

@ -1,5 +1,6 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.TestPlan;
import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
@ -32,4 +33,6 @@ public interface ExtTestPlanMapper {
String findScheduleCreateUserById(String testPlanId);
List<String> findIdByPerformanceReportId(String reportId);
List<TestPlan> listRecent(@Param("userId") String userId, @Param("projectId") String currentProjectId);
}

View File

@ -240,5 +240,16 @@
WHERE reportData.performance_info like CONCAT('%', #{0},'%')
AND report.is_performance_executing = true;
</select>
<select id="listRecent" resultType="io.metersphere.base.domain.TestPlan">
select distinct test_plan.*
from test_plan
<where>
<if test="projectId != null">
and test_plan.project_id = #{projectId}
</if>
and (test_plan.creator = #{userId} or test_plan.principal = #{userId})
</where>
order by test_plan.update_time desc
</select>
</mapper>

View File

@ -4,9 +4,10 @@
<select id="list" resultType="io.metersphere.track.dto.TestPlanReportDTO"
parameterType="io.metersphere.track.request.report.QueryTestPlanReportRequest">
SELECT tpr.id AS id, tpr.`name` AS `name`, tp.`name` AS testPlanName, tpr.creator AS creator, tpr.create_time AS createTime,tpr.trigger_Mode AS triggerMode
SELECT tpr.id AS id, tpr.`name` AS `name`, tp.`name` AS testPlanName, u.name AS creator, tpr.create_time AS createTime,tpr.trigger_Mode AS triggerMode,tpr.status AS status
FROM test_plan tp
INNER JOIN test_plan_report tpr on tp.id = tpr.test_plan_id
INNER JOIN user u on tpr.creator = u.id
<where>
<if test="name != null">
and tpr.name like CONCAT('%', #{name},'%')

View File

@ -27,6 +27,7 @@ public class ShiroUtils {
filterChainDefinitionMap.put("/jmeter/download/**", "anon");
filterChainDefinitionMap.put("/authsource/list/allenable", "anon");
filterChainDefinitionMap.put("/sso/signin", "anon");
filterChainDefinitionMap.put("/sso/callback", "anon");
// for swagger
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-ui/**", "anon");

View File

@ -66,9 +66,8 @@ public class TestPlanController {
@GetMapping("recent/{count}")
public List<TestPlan> recentTestPlans(@PathVariable int count) {
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
PageHelper.startPage(1, count, true);
return testPlanService.recentTestPlans(currentWorkspaceId);
return testPlanService.recentTestPlans();
}
@PostMapping("/get/{testPlanId}")

View File

@ -54,6 +54,12 @@ public class TestPlanReportController {
testPlanReportService.delete(testPlanReportIdList);
}
@PostMapping("/deleteBatchByParams")
public void deleteBatchByParams(@RequestBody QueryTestPlanReportRequest request) {
testPlanReportService.delete(request);
}
@GetMapping("/apiExecuteFinish/{planId}/{userId}")
public void apiExecuteFinish(@PathVariable String planId,@PathVariable String userId) {
TestPlanReport report = testPlanReportService.genTestPlanReport(planId,userId,ReportTriggerMode.API.name());

View File

@ -20,6 +20,7 @@ public class TestPlanReportDTO {
private String creator;
private long createTime;
private String triggerMode;
private String status;
private TestCaseReportAdvanceStatusResultDTO executeResult;
private List<TestCaseReportModuleResultDTO> moduleExecuteResult;

View File

@ -25,5 +25,11 @@ public class QueryTestPlanReportRequest {
private List<OrderRequest> orders;
private Map<String, List<String>> filters;
// private Map<String, Object> combine;
/**
* 批量操作的参数用于判断是前台表格的当前页数据还是全库数据
*/
private boolean isSelectAllDate;
private List<String> unSelectIds;
private List<String> dataIds;
}

View File

@ -1,10 +1,12 @@
package io.metersphere.track.request.testplan;
import io.metersphere.base.domain.TestPlanLoadCase;
import io.metersphere.controller.request.OrderRequest;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Getter
@Setter
@ -12,4 +14,6 @@ public class LoadCaseRequest extends TestPlanLoadCase {
private String projectId;
private List<String> caseIds;
private String name;
private Map<String, List<String>> filters;
private List<OrderRequest> orders;
}

View File

@ -32,7 +32,8 @@ public class LoadReportStatusEvent implements LoadTestFinishEvent {
@Override
public void execute(LoadTestReport loadTestReport) {
if (StringUtils.equals(ReportTriggerMode.CASE.name(), loadTestReport.getTriggerMode())) {
if (StringUtils.equals(ReportTriggerMode.CASE.name(), loadTestReport.getTriggerMode())
|| StringUtils.equals(ReportTriggerMode.TEST_PLAN_SCHEDULE.name(), loadTestReport.getTriggerMode())) {
if (StringUtils.equalsAny(loadTestReport.getStatus(),
PerformanceTestStatus.Completed.name(), PerformanceTestStatus.Error.name())) {
updateLoadCaseStatus(loadTestReport);

View File

@ -5,6 +5,7 @@ import io.metersphere.base.mapper.LoadTestMapper;
import io.metersphere.base.mapper.LoadTestReportMapper;
import io.metersphere.base.mapper.TestPlanLoadCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanLoadCaseMapper;
import io.metersphere.controller.request.OrderRequest;
import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.track.dto.TestPlanLoadCaseDTO;
import io.metersphere.track.request.testplan.LoadCaseReportRequest;
@ -50,6 +51,15 @@ public class TestPlanLoadCaseService {
}
public List<TestPlanLoadCaseDTO> list(LoadCaseRequest request) {
List<OrderRequest> orders = request.getOrders();
if (orders == null || orders.size() < 1) {
OrderRequest orderRequest = new OrderRequest();
orderRequest.setName("create_time");
orderRequest.setType("desc");
orders = new ArrayList<>();
orders.add(orderRequest);
}
request.setOrders(orders);
return extTestPlanLoadCaseMapper.selectTestPlanLoadCaseList(request);
}
@ -129,4 +139,8 @@ public class TestPlanLoadCaseService {
testPlanLoadCaseMapper.updateByPrimaryKeySelective(testPlanLoadCase);
}
}
public List<String> getStatus(String planId) {
return extTestPlanLoadCaseMapper.getStatusByTestPlanId(planId);
}
}

View File

@ -2,6 +2,8 @@ package io.metersphere.track.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.definition.ApiDefinitionRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
@ -109,7 +111,6 @@ public class TestPlanReportService {
TestPlanReport testPlanReport = new TestPlanReport();
testPlanReport.setTestPlanId(planId);
testPlanReport.setId(testPlanReportID);
testPlanReport.setStatus(APITestStatus.Starting.name());
testPlanReport.setCreateTime(System.currentTimeMillis());
testPlanReport.setUpdateTime(System.currentTimeMillis());
try {
@ -136,6 +137,12 @@ public class TestPlanReportService {
testPlanReport.setIsPerformanceExecuting(true);
}
testPlanReport.setPrincipal(testPlan.getPrincipal());
if(testPlanReport.getIsScenarioExecuting() || testPlanReport.getIsApiCaseExecuting() || testPlanReport.getIsPerformanceExecuting()){
testPlanReport.setStatus(APITestStatus.Starting.name());
}else {
testPlanReport.setStatus(APITestStatus.Completed.name());
}
testPlanReportMapper.insert(testPlanReport);
TestPlanReportDataWithBLOBs testPlanReportData = new TestPlanReportDataWithBLOBs();
@ -327,6 +334,7 @@ public class TestPlanReportService {
} catch (Exception e) {
}
}else {
}
testPlanReportMapper.updateByPrimaryKey(report);
}
@ -407,4 +415,33 @@ public class TestPlanReportService {
testPlanReportDataMapper.deleteByExample(example);
}
}
public void delete(QueryTestPlanReportRequest request) {
List<String> deleteReportIds = request.getDataIds();
if (request.isSelectAllDate()) {
deleteReportIds = this.getAllApiIdsByFontedSelect(request.getFilters(), request.getName(), request.getProjectId(), request.getUnSelectIds());
}
TestPlanReportExample deleteReportExample = new TestPlanReportExample();
deleteReportExample.createCriteria().andIdIn(deleteReportIds);
testPlanReportMapper.deleteByExample(deleteReportExample);
TestPlanReportDataExample example = new TestPlanReportDataExample();
example.createCriteria().andTestPlanReportIdIn(deleteReportIds);
testPlanReportDataMapper.deleteByExample(example);
}
private List<String> getAllApiIdsByFontedSelect(Map<String, List<String>> filters, String name, String projectId, List<String> unSelectIds) {
QueryTestPlanReportRequest request = new QueryTestPlanReportRequest();
request.setFilters(filters);
request.setName(name);
request.setProjectId(projectId);
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
List<TestPlanReportDTO> resList = extTestPlanReportMapper.list(request);
List<String> ids = new ArrayList<>(0);
if (!resList.isEmpty()) {
List<String> allIds = resList.stream().map(TestPlanReportDTO::getId).collect(Collectors.toList());
ids = allIds.stream().filter(id -> !unSelectIds.contains(id)).collect(Collectors.toList());
}
return ids;
}
}

View File

@ -19,10 +19,7 @@ import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
import io.metersphere.base.mapper.ext.*;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
@ -103,6 +100,12 @@ public class TestPlanService {
private JMeterService jMeterService;
@Resource
private ApiAutomationService apiAutomationService;
@Resource
private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource
private ExtTestPlanLoadCaseMapper extTestPlanLoadCaseMapper;
@Resource
private ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper;
public synchronized void addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
@ -379,7 +382,17 @@ public class TestPlanService {
}
});
testPlan.setTotal(apiExecResults.size() + scenarioExecResults.size() + functionalExecResults.size());
List<String> loadResults = testPlanLoadCaseService.getStatus(testPlan.getId());
loadResults.forEach(item -> {
if (StringUtils.isNotBlank(item)) {
testPlan.setTested(testPlan.getTested() + 1);
if (StringUtils.equals(item, "success")) {
testPlan.setPassed(testPlan.getPassed() + 1);
}
}
});
testPlan.setTotal(apiExecResults.size() + scenarioExecResults.size() + functionalExecResults.size() + loadResults.size());
testPlan.setPassRate(MathUtils.getPercentWithDecimal(testPlan.getTested() == 0 ? 0 : testPlan.getPassed() * 1.0 / testPlan.getTested()));
testPlan.setTestRate(MathUtils.getPercentWithDecimal(testPlan.getTotal() == 0 ? 0 : testPlan.getTested() * 1.0 / testPlan.getTotal()));
@ -454,26 +467,8 @@ public class TestPlanService {
}
}
public List<TestPlan> recentTestPlans(String currentWorkspaceId) {
if (StringUtils.isBlank(currentWorkspaceId)) {
return null;
}
if (StringUtils.isNotBlank(SessionUtils.getCurrentProjectId())) {
TestPlanExample testPlanExample = new TestPlanExample();
TestPlanExample.Criteria criteria = testPlanExample.createCriteria();
criteria.andProjectIdEqualTo(SessionUtils.getCurrentProjectId());
List<TestPlan> testPlans = testPlanMapper.selectByExample(testPlanExample);
if (!CollectionUtils.isEmpty(testPlans)) {
List<String> testPlanIds = testPlans.stream().map(TestPlan::getId).collect(Collectors.toList());
TestPlanExample testPlanTestCaseExample = new TestPlanExample();
testPlanTestCaseExample.createCriteria().andWorkspaceIdEqualTo(currentWorkspaceId)
.andIdIn(testPlanIds)
.andPrincipalEqualTo(SessionUtils.getUserId());
testPlanTestCaseExample.setOrderByClause("update_time desc");
return testPlanMapper.selectByExample(testPlanTestCaseExample);
}
}
return new ArrayList<>();
public List<TestPlan> recentTestPlans() {
return extTestPlanMapper.listRecent(SessionUtils.getUserId(), SessionUtils.getCurrentProjectId());
}
public List<TestPlan> listTestAllPlan(String currentWorkspaceId) {
@ -550,18 +545,10 @@ public class TestPlanService {
}
public void editTestPlanStatus(String planId) {
List<String> statusList = extTestPlanTestCaseMapper.getStatusByPlanId(planId);
TestPlan testPlan = new TestPlan();
testPlan.setId(planId);
for (String status : statusList) {
if (StringUtils.equals(status, TestPlanTestCaseStatus.Prepare.name())
|| StringUtils.equals(status, TestPlanTestCaseStatus.Underway.name())) {
testPlan.setStatus(TestPlanStatus.Underway.name());
testPlanMapper.updateByPrimaryKeySelective(testPlan);
return;
}
}
testPlan.setStatus(TestPlanStatus.Completed.name());
String status = calcTestPlanStatus(planId);
testPlan.setStatus(status);
testPlanMapper.updateByPrimaryKeySelective(testPlan);
TestPlan testPlans = getTestPlan(planId);
List<String> userIds = new ArrayList<>();
@ -590,6 +577,44 @@ public class TestPlanService {
}
private String calcTestPlanStatus(String planId) {
// test-plan-functional-case status
List<String> funcStatusList = extTestPlanTestCaseMapper.getStatusByPlanId(planId);
for (String funcStatus : funcStatusList) {
if (StringUtils.equals(funcStatus, TestPlanTestCaseStatus.Prepare.name())
|| StringUtils.equals(funcStatus, TestPlanTestCaseStatus.Underway.name())) {
return TestPlanStatus.Underway.name();
}
}
// test-plan-api-case status
List<String> apiStatusList = extTestPlanApiCaseMapper.getStatusByTestPlanId(planId);
for (String apiStatus : apiStatusList) {
if (apiStatus == null) {
return TestPlanStatus.Underway.name();
}
}
// test-plan-scenario-case status
List<String> scenarioStatusList = extTestPlanScenarioCaseMapper.getExecResultByPlanId(planId);
for (String scenarioStatus : scenarioStatusList) {
if (scenarioStatus == null) {
return TestPlanStatus.Underway.name();
}
}
// test-plan-load-case status
List<String> loadStatusList = extTestPlanLoadCaseMapper.getStatusByTestPlanId(planId);
for (String loadStatus : loadStatusList) {
if (loadStatus == null) {
return TestPlanStatus.Underway.name();
}
}
return TestPlanStatus.Completed.name();
}
public String getProjectNameByPlanId(String testPlanId) {
List<String> projectIds = testPlanProjectService.getProjectIdsByPlanId(testPlanId);
ProjectExample projectExample = new ProjectExample();

@ -1 +1 @@
Subproject commit 8d175b5363274672ff33a5883b730e8aaa823f8d
Subproject commit e4521190f0f1be113c8b84fbdcdf8ff273bf274e

View File

@ -110,7 +110,6 @@
if (this.isNotRunning) {
try {
this.content = JSON.parse(this.report.content);
console.log(this.content)
if (!this.content) {
this.content = {scenarios: []};
}

View File

@ -6,7 +6,10 @@
:show-create="false"/>
</template>
<el-table ref="scenarioTable" border :data="tableData" class="adjust-table ms-select-all" @select-all="select" @select="select"
<el-table ref="scenarioTable" border :data="tableData" class="adjust-table ms-select-all"
@sort-change="sort"
@filter-change="filter"
@select-all="select" @select="select"
v-loading="loading">
<el-table-column type="selection" width="50"/>
@ -24,36 +27,53 @@
</el-table-column>
<el-table-column prop="num" label="ID"
sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="name" :label="$t('api_test.automation.scenario_name')"
show-overflow-tooltip/>
<el-table-column prop="level" :label="$t('api_test.automation.case_level')"
show-overflow-tooltip>
<el-table-column prop="name"
sortable="custom"
:label="$t('api_test.automation.scenario_name')"
show-overflow-tooltip
min-width="100px"/>
<el-table-column prop="level"
sortable="custom"
column-key="level"
:filters="levelFilters"
:label="$t('api_test.automation.case_level')"
show-overflow-tooltip min-width="120px">
<template v-slot:default="scope">
<priority-table-item :value="scope.row.level"/>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('test_track.plan.plan_status')"
show-overflow-tooltip>
sortable="custom"
column-key="status"
:filters="statusFilters"
show-overflow-tooltip min-width="120px">
<template v-slot:default="scope">
<plan-status-table-item :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column prop="tags" :label="$t('api_test.automation.tag')" width="200px">
<el-table-column prop="tags" :label="$t('api_test.automation.tag')">
<template v-slot:default="scope">
<div v-for="(itemName,index) in scope.row.tags" :key="index">
<ms-tag type="success" effect="plain" :content="itemName"/>
</div>
</template>
</el-table-column>
<el-table-column prop="userId" :label="$t('api_test.automation.creator')" show-overflow-tooltip/>
<el-table-column prop="updateTime" :label="$t('api_test.automation.update_time')" width="180">
<el-table-column prop="userId" :label="$t('api_test.automation.creator')"
:filters="userFilters"
column-key="user_id"
sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="updateTime" :label="$t('api_test.automation.update_time')" sortable="custom" width="180">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="stepTotal" :label="$t('api_test.automation.step')" show-overflow-tooltip/>
<el-table-column prop="lastResult" :label="$t('api_test.automation.last_result')">
<el-table-column prop="lastResult" :label="$t('api_test.automation.last_result')"
:filters="resultFilters"
sortable="custom" column-key="last_result" min-width="120px">
<template v-slot:default="{row}">
<el-link type="success" @click="showReport(row)" v-if="row.lastResult === 'Success'">
{{ $t('api_test.automation.success') }}
@ -112,436 +132,473 @@
</template>
<script>
import MsTableHeader from "@/business/components/common/components/MsTableHeader";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import ShowMoreBtn from "@/business/components/track/case/components/ShowMoreBtn";
import MsTag from "../../../common/components/MsTag";
import {getUUID, getCurrentProjectID} from "@/common/js/utils";
import MsApiReportDetail from "../report/ApiReportDetail";
import MsTableMoreBtn from "./TableMoreBtn";
import MsScenarioExtendButtons from "@/business/components/api/automation/scenario/ScenarioExtendBtns";
import MsTestPlanList from "./testplan/TestPlanList";
import MsTableSelectAll from "../../../common/components/table/MsTableSelectAll";
import {API_CASE_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
import PriorityTableItem from "../../../track/common/tableItems/planview/PriorityTableItem";
import PlanStatusTableItem from "../../../track/common/tableItems/plan/PlanStatusTableItem";
import BatchEdit from "../../../track/case/components/BatchEdit";
import {WORKSPACE_ID} from "../../../../../common/js/constants";
import EnvironmentSelect from "../../definition/components/environment/EnvironmentSelect";
import BatchMove from "../../../track/case/components/BatchMove";
import MsTableHeader from "@/business/components/common/components/MsTableHeader";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import ShowMoreBtn from "@/business/components/track/case/components/ShowMoreBtn";
import MsTag from "../../../common/components/MsTag";
import {_filter, _sort, getCurrentProjectID, getUUID} from "@/common/js/utils";
import MsApiReportDetail from "../report/ApiReportDetail";
import MsTableMoreBtn from "./TableMoreBtn";
import MsScenarioExtendButtons from "@/business/components/api/automation/scenario/ScenarioExtendBtns";
import MsTestPlanList from "./testplan/TestPlanList";
import MsTableSelectAll from "../../../common/components/table/MsTableSelectAll";
import {API_CASE_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
import PriorityTableItem from "../../../track/common/tableItems/planview/PriorityTableItem";
import PlanStatusTableItem from "../../../track/common/tableItems/plan/PlanStatusTableItem";
import BatchEdit from "../../../track/case/components/BatchEdit";
import {WORKSPACE_ID} from "../../../../../common/js/constants";
import EnvironmentSelect from "../../definition/components/environment/EnvironmentSelect";
import BatchMove from "../../../track/case/components/BatchMove";
export default {
name: "MsApiScenarioList",
components: {
BatchMove,
EnvironmentSelect,
BatchEdit,
PlanStatusTableItem,
PriorityTableItem,
MsTableSelectAll,
MsTablePagination,
MsTableMoreBtn,
ShowMoreBtn,
MsTableHeader,
MsTag,
MsApiReportDetail,
MsScenarioExtendButtons,
MsTestPlanList,
MsTableOperatorButton
export default {
name: "MsApiScenarioList",
components: {
BatchMove,
EnvironmentSelect,
BatchEdit,
PlanStatusTableItem,
PriorityTableItem,
MsTableSelectAll,
MsTablePagination,
MsTableMoreBtn,
ShowMoreBtn,
MsTableHeader,
MsTag,
MsApiReportDetail,
MsScenarioExtendButtons,
MsTestPlanList,
MsTableOperatorButton
},
props: {
referenced: {
type: Boolean,
default: false,
},
props: {
referenced: {
type: Boolean,
default: false,
},
selectNodeIds: Array,
trashEnable: {
type: Boolean,
default: false,
},
moduleTree: {
type: Array,
default() {
return []
},
},
moduleOptions: {
type: Array,
default() {
return []
},
}
selectNodeIds: Array,
trashEnable: {
type: Boolean,
default: false,
},
data() {
return {
loading: false,
condition: {
components: API_CASE_CONFIGS
},
currentScenario: {},
schedule: {},
selection: [],
tableData: [],
selectDataRange: 'all',
currentPage: 1,
pageSize: 10,
total: 0,
reportId: "",
batchReportId: "",
content: {},
infoDb: false,
runVisible: false,
planVisible: false,
projectId: "",
runData: [],
report: {},
selectDataSize: 0,
selectAll: false,
buttons: [
{
name: this.$t('api_test.automation.batch_add_plan'), handleClick: this.handleBatchAddCase
},
{
name: this.$t('api_test.automation.batch_execute'), handleClick: this.handleBatchExecute
},
{
name: this.$t('test_track.case.batch_edit_case'), handleClick: this.handleBatchEdit
},
{
name: this.$t('test_track.case.batch_move_case'), handleClick: this.handleBatchMove
}
],
isSelectAllDate: false,
unSelection: [],
selectDataCounts: 0,
typeArr: [
{id: 'level', name: this.$t('test_track.case.priority')},
{id: 'status', name: this.$t('test_track.plan.plan_status')},
{id: 'principal', name: this.$t('api_test.definition.request.responsible'), optionMethod: this.getPrincipalOptions},
{id: 'environmentId', name: this.$t('api_test.definition.request.run_env'), optionMethod: this.getEnvsOptions},
],
valueArr: {
level: [
{name: 'P0', id: 'P0'},
{name: 'P1', id: 'P1'},
{name: 'P2', id: 'P2'},
{name: 'P3', id: 'P3'}
],
status: [
{name: this.$t('test_track.plan.plan_status_prepare'), id: 'Prepare'},
{name: this.$t('test_track.plan.plan_status_running'), id: 'Underway'},
{name: this.$t('test_track.plan.plan_status_completed'), id: 'Completed'}
],
principal: [],
environmentId: []
},
}
moduleTree: {
type: Array,
default() {
return []
},
},
created() {
this.projectId = getCurrentProjectID();
this.search();
},
watch: {
selectNodeIds() {
this.search();
},
trashEnable() {
if (this.trashEnable) {
this.search();
}
},
batchReportId() {
this.loading = true;
this.getReport();
}
},
computed: {
isNotRunning() {
return "Running" !== this.report.status;
}
},
methods: {
selectByParam() {
this.changeSelectDataRangeAll();
this.search();
},
search() {
this.condition.filters = ["Prepare", "Underway", "Completed"];
this.condition.moduleIds = this.selectNodeIds;
if (this.trashEnable) {
this.condition.filters = ["Trash"];
this.condition.moduleIds = [];
}
if (this.projectId != null) {
this.condition.projectId = this.projectId;
}
//
this.condition.selectThisWeedData = false;
this.condition.executeStatus = null;
this.isSelectThissWeekData();
switch (this.selectDataRange) {
case 'thisWeekCount':
this.condition.selectThisWeedData = true;
break;
case 'unExecute':
this.condition.executeStatus = 'unExecute';
break;
case 'executeFailed':
this.condition.executeStatus = 'executeFailed';
break;
case 'executePass':
this.condition.executeStatus = 'executePass';
break;
}
this.selection = [];
this.selectAll = false;
this.unSelection = [];
this.selectDataCounts = 0;
let url = "/api/automation/list/" + this.currentPage + "/" + this.pageSize;
if (this.condition.projectId) {
this.loading = true;
this.$post(url, this.condition, response => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
}
});
this.loading = false;
this.unSelection = data.listObject.map(s => s.id);
});
}
},
handleCommand(cmd) {
let table = this.$refs.scenarioTable;
switch (cmd) {
case "table":
this.selectAll = false;
table.toggleAllSelection();
break;
case "all":
this.selectAll = true;
break
}
},
handleBatchAddCase() {
this.planVisible = true;
},
handleBatchEdit() {
this.$refs.batchEdit.open(this.selectDataCounts);
},
handleBatchMove() {
this.$refs.testBatchMove.open(this.moduleTree, [], this.moduleOptions);
},
moveSave(param) {
this.buildBatchParam(param);
param.apiScenarioModuleId = param.nodeId;
this.$post('/api/automation/batch/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.$refs.testBatchMove.close();
this.search();
});
},
batchEdit(form) {
let param = {};
param[form.type] = form.value;
this.buildBatchParam(param);
this.$post('/api/automation/batch/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.search();
});
},
getPrincipalOptions(option) {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
option.push(...response.data);
});
},
getEnvsOptions(option) {
this.$get('/api/environment/list/' + this.projectId, response => {
option.push(...response.data);
option.forEach(environment => {
if (!(environment.config instanceof Object)) {
environment.config = JSON.parse(environment.config);
}
environment.name = environment.name + (environment.config.httpConfig.socket ?
(': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '');
});
});
},
addTestPlan(plans) {
let obj = {planIds: plans, scenarioIds: this.selection};
obj.projectId = getCurrentProjectID();
obj.selectAllDate = this.isSelectAllDate;
obj.unSelectIds = this.unSelection;
obj = Object.assign(obj, this.condition);
this.planVisible = false;
this.$post("/api/automation/scenario/plan", obj, response => {
this.$success(this.$t("commons.save_success"));
});
},
getReport() {
if (this.batchReportId) {
let url = "/api/scenario/report/get/" + this.batchReportId;
this.$get(url, response => {
this.report = response.data || {};
if (response.data) {
if (this.isNotRunning) {
try {
this.content = JSON.parse(this.report.content);
} catch (e) {
throw e;
}
this.loading = false;
this.$success("批量执行成功,请到报告页面查看详情!");
} else {
setTimeout(this.getReport, 2000)
}
} else {
this.loading = false;
this.$error(this.$t('api_report.not_exist'));
}
});
}
},
buildBatchParam(param) {
param.scenarioIds = this.selection;
param.projectId = getCurrentProjectID();
param.selectAllDate = this.isSelectAllDate;
param.unSelectIds = this.unSelection;
param = Object.assign(param, this.condition);
},
handleBatchExecute() {
this.infoDb = false;
let url = "/api/automation/run/batch";
let run = {};
run.id = getUUID();
this.buildBatchParam(run);
this.$post(url, run, response => {
let data = response.data;
this.runVisible = false;
this.batchReportId = run.id;
});
},
select(selection) {
this.selection = selection.map(s => s.id);
//
this.selectRowsCount(this.selection)
this.$emit('selection', selection);
},
isSelect(row) {
return this.selection.includes(row.id)
},
edit(row) {
let data = JSON.parse(JSON.stringify(row));
this.$emit('edit', data);
},
reductionApi(row) {
row.scenarioDefinition = null;
row.tags = null;
let rows = [row];
this.$post("/api/automation/reduction", rows, response => {
this.$success(this.$t('commons.save_success'));
this.search();
})
},
execute(row) {
this.infoDb = false;
let url = "/api/automation/run";
let run = {};
let scenarioIds = [];
scenarioIds.push(row.id);
run.id = getUUID();
run.projectId = getCurrentProjectID();
run.scenarioIds = scenarioIds;
this.$post(url, run, response => {
let data = response.data;
this.runVisible = true;
this.reportId = run.id;
});
},
copy(row) {
let rowParam = JSON.parse(JSON.stringify(row));
rowParam.copy = true;
rowParam.name = 'copy_' + rowParam.name;
this.$emit('edit', rowParam);
},
showReport(row) {
this.runVisible = true;
this.infoDb = true;
this.reportId = row.reportId;
},
//
isSelectDataAll(dataType) {
this.isSelectAllDate = dataType;
this.selectRowsCount(this.selection);
//
if (this.selection.length != this.tableData.length) {
this.$refs.scenarioTable.toggleAllSelection(true);
}
},
//
selectRowsCount(selection) {
let selectedIDs = selection;
let allIDs = this.tableData.map(s => s.id);
this.unSelection = allIDs.filter(function (val) {
return selectedIDs.indexOf(val) === -1
});
if (this.isSelectAllDate) {
this.selectDataCounts = this.total - this.unSelection.length;
} else {
this.selectDataCounts = this.selection.length;
}
},
//
isSelectThissWeekData() {
let dataRange = this.$route.params.dataSelectRange;
this.selectDataRange = dataRange;
},
changeSelectDataRangeAll() {
this.$emit("changeSelectDataRangeAll");
},
remove(row) {
if (this.trashEnable) {
this.$get('/api/automation/delete/' + row.id, () => {
this.$success(this.$t('commons.delete_success'));
this.search();
});
return;
}
this.$alert(this.$t('api_test.definition.request.delete_confirm') + ' ' + row.name + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = [row.id];
this.$post('/api/automation/removeToGc/', ids, () => {
this.$success(this.$t('commons.delete_success'));
this.search();
});
}
}
});
moduleOptions: {
type: Array,
default() {
return []
},
}
},
data() {
return {
loading: false,
condition: {
components: API_CASE_CONFIGS
},
currentScenario: {},
schedule: {},
selection: [],
tableData: [],
selectDataRange: 'all',
currentPage: 1,
pageSize: 10,
total: 0,
reportId: "",
batchReportId: "",
content: {},
infoDb: false,
runVisible: false,
planVisible: false,
projectId: "",
runData: [],
report: {},
selectDataSize: 0,
selectAll: false,
userFilters: [],
buttons: [
{
name: this.$t('api_test.automation.batch_add_plan'), handleClick: this.handleBatchAddCase
},
{
name: this.$t('api_test.automation.batch_execute'), handleClick: this.handleBatchExecute
},
{
name: this.$t('test_track.case.batch_edit_case'), handleClick: this.handleBatchEdit
},
{
name: this.$t('test_track.case.batch_move_case'), handleClick: this.handleBatchMove
}
],
isSelectAllDate: false,
unSelection: [],
selectDataCounts: 0,
typeArr: [
{id: 'level', name: this.$t('test_track.case.priority')},
{id: 'status', name: this.$t('test_track.plan.plan_status')},
{id: 'principal', name: this.$t('api_test.definition.request.responsible'), optionMethod: this.getPrincipalOptions},
{id: 'environmentId', name: this.$t('api_test.definition.request.run_env'), optionMethod: this.getEnvsOptions},
],
statusFilters: [
{text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'},
{text: this.$t('test_track.plan.plan_status_running'), value: 'Underway'},
{text: this.$t('test_track.plan.plan_status_completed'), value: 'Completed'},
{text: this.$t('test_track.plan.plan_status_trash'), value: 'Trash'},
],
levelFilters: [
{text: 'P0', value: 'P0'},
{text: 'P1', value: 'P1'},
{text: 'P2', value: 'P2'},
{text: 'P3', value: 'P3'}
],
resultFilters: [
{text: 'Fail', value: 'Fail'},
{text: 'Success', value: 'Success'}
],
valueArr: {
level: [
{name: 'P0', id: 'P0'},
{name: 'P1', id: 'P1'},
{name: 'P2', id: 'P2'},
{name: 'P3', id: 'P3'}
],
status: [
{name: this.$t('test_track.plan.plan_status_prepare'), id: 'Prepare'},
{name: this.$t('test_track.plan.plan_status_running'), id: 'Underway'},
{name: this.$t('test_track.plan.plan_status_completed'), id: 'Completed'}
],
principal: [],
environmentId: []
},
}
},
created() {
this.condition.filters = {status: ["Prepare", "Underway", "Completed"]};
this.projectId = getCurrentProjectID();
this.search();
this.getPrincipalOptions([]);
},
watch: {
selectNodeIds() {
this.search();
},
trashEnable() {
if (this.trashEnable) {
this.condition.filters = {status: ["Trash"]};
this.condition.moduleIds = [];
} else {
this.condition.filters = {status: ["Prepare", "Underway", "Completed"]};
}
this.search();
},
batchReportId() {
this.loading = true;
this.getReport();
}
},
computed: {
isNotRunning() {
return "Running" !== this.report.status;
}
},
methods: {
selectByParam() {
this.changeSelectDataRangeAll();
this.search();
},
search() {
this.condition.moduleIds = this.selectNodeIds;
if (this.trashEnable) {
this.condition.filters = {status: ["Trash"]};
this.condition.moduleIds = [];
}
if (this.projectId != null) {
this.condition.projectId = this.projectId;
}
//
this.condition.selectThisWeedData = false;
this.condition.executeStatus = null;
this.isSelectThissWeekData();
switch (this.selectDataRange) {
case 'thisWeekCount':
this.condition.selectThisWeedData = true;
break;
case 'unExecute':
this.condition.executeStatus = 'unExecute';
break;
case 'executeFailed':
this.condition.executeStatus = 'executeFailed';
break;
case 'executePass':
this.condition.executeStatus = 'executePass';
break;
}
this.selection = [];
this.selectAll = false;
this.unSelection = [];
this.selectDataCounts = 0;
let url = "/api/automation/list/" + this.currentPage + "/" + this.pageSize;
if (this.condition.projectId) {
this.loading = true;
this.$post(url, this.condition, response => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
}
});
this.loading = false;
this.unSelection = data.listObject.map(s => s.id);
});
}
},
handleCommand(cmd) {
let table = this.$refs.scenarioTable;
switch (cmd) {
case "table":
this.selectAll = false;
table.toggleAllSelection();
break;
case "all":
this.selectAll = true;
break
}
},
handleBatchAddCase() {
this.planVisible = true;
},
handleBatchEdit() {
this.$refs.batchEdit.open(this.selectDataCounts);
},
handleBatchMove() {
this.$refs.testBatchMove.open(this.moduleTree, [], this.moduleOptions);
},
moveSave(param) {
this.buildBatchParam(param);
param.apiScenarioModuleId = param.nodeId;
this.$post('/api/automation/batch/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.$refs.testBatchMove.close();
this.search();
});
},
batchEdit(form) {
let param = {};
param[form.type] = form.value;
this.buildBatchParam(param);
this.$post('/api/automation/batch/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.search();
});
},
getPrincipalOptions(option) {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
option.push(...response.data);
this.userFilters = response.data.map(u => {
return {text: u.name, value: u.id}
});
});
},
getEnvsOptions(option) {
this.$get('/api/environment/list/' + this.projectId, response => {
option.push(...response.data);
option.forEach(environment => {
if (!(environment.config instanceof Object)) {
environment.config = JSON.parse(environment.config);
}
environment.name = environment.name + (environment.config.httpConfig.socket ?
(': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '');
});
});
},
addTestPlan(plans) {
let obj = {planIds: plans, scenarioIds: this.selection};
obj.projectId = getCurrentProjectID();
obj.selectAllDate = this.isSelectAllDate;
obj.unSelectIds = this.unSelection;
obj = Object.assign(obj, this.condition);
this.planVisible = false;
this.$post("/api/automation/scenario/plan", obj, response => {
this.$success(this.$t("commons.save_success"));
});
},
getReport() {
if (this.batchReportId) {
let url = "/api/scenario/report/get/" + this.batchReportId;
this.$get(url, response => {
this.report = response.data || {};
if (response.data) {
if (this.isNotRunning) {
try {
this.content = JSON.parse(this.report.content);
} catch (e) {
throw e;
}
this.loading = false;
this.$success("批量执行成功,请到报告页面查看详情!");
} else {
setTimeout(this.getReport, 2000)
}
} else {
this.loading = false;
this.$error(this.$t('api_report.not_exist'));
}
});
}
},
buildBatchParam(param) {
param.scenarioIds = this.selection;
param.projectId = getCurrentProjectID();
param.selectAllDate = this.isSelectAllDate;
param.unSelectIds = this.unSelection;
param = Object.assign(param, this.condition);
},
handleBatchExecute() {
this.infoDb = false;
let url = "/api/automation/run/batch";
let run = {};
run.id = getUUID();
this.buildBatchParam(run);
this.$post(url, run, response => {
let data = response.data;
this.runVisible = false;
this.batchReportId = run.id;
});
},
select(selection) {
this.selection = selection.map(s => s.id);
//
this.selectRowsCount(this.selection)
this.$emit('selection', selection);
},
isSelect(row) {
return this.selection.includes(row.id)
},
edit(row) {
let data = JSON.parse(JSON.stringify(row));
this.$emit('edit', data);
},
reductionApi(row) {
row.scenarioDefinition = null;
row.tags = null;
let rows = [row];
this.$post("/api/automation/reduction", rows, response => {
this.$success(this.$t('commons.save_success'));
this.search();
})
},
execute(row) {
this.infoDb = false;
let url = "/api/automation/run";
let run = {};
let scenarioIds = [];
scenarioIds.push(row.id);
run.id = getUUID();
run.projectId = getCurrentProjectID();
run.scenarioIds = scenarioIds;
this.$post(url, run, response => {
let data = response.data;
this.runVisible = true;
this.reportId = run.id;
});
},
copy(row) {
let rowParam = JSON.parse(JSON.stringify(row));
rowParam.copy = true;
rowParam.name = 'copy_' + rowParam.name;
this.$emit('edit', rowParam);
},
showReport(row) {
this.runVisible = true;
this.infoDb = true;
this.reportId = row.reportId;
},
//
isSelectDataAll(dataType) {
this.isSelectAllDate = dataType;
this.selectRowsCount(this.selection);
//
if (this.selection.length != this.tableData.length) {
this.$refs.scenarioTable.toggleAllSelection(true);
}
},
//
selectRowsCount(selection) {
let selectedIDs = selection;
let allIDs = this.tableData.map(s => s.id);
this.unSelection = allIDs.filter(function (val) {
return selectedIDs.indexOf(val) === -1
});
if (this.isSelectAllDate) {
this.selectDataCounts = this.total - this.unSelection.length;
} else {
this.selectDataCounts = this.selection.length;
}
},
//
isSelectThissWeekData() {
let dataRange = this.$route.params.dataSelectRange;
this.selectDataRange = dataRange;
},
changeSelectDataRangeAll() {
this.$emit("changeSelectDataRangeAll");
},
remove(row) {
if (this.trashEnable) {
this.$get('/api/automation/delete/' + row.id, () => {
this.$success(this.$t('commons.delete_success'));
this.search();
});
return;
}
this.$alert(this.$t('api_test.definition.request.delete_confirm') + ' ' + row.name + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = [row.id];
this.$post('/api/automation/removeToGc/', ids, () => {
this.$success(this.$t('commons.delete_success'));
this.search();
});
}
}
});
},
sort(column) {
//
if (this.condition.orders) {
this.condition.orders = [];
}
_sort(column, this.condition);
this.search();
},
filter(filters) {
_filter(filters, this.condition);
this.search();
},
}
}
</script>
<style scoped>
/deep/ .el-drawer__header {
margin-bottom: 0px;
}
/deep/ .el-drawer__header {
margin-bottom: 0px;
}
/deep/ .run-button {
background-color: #409EFF;
border-color: #409EFF;
}
/deep/ .run-button {
background-color: #409EFF;
border-color: #409EFF;
}
</style>

View File

@ -143,7 +143,7 @@
highlight-current
@node-expand="nodeExpand"
@node-collapse="nodeCollapse"
:allow-drop="allowDrop" @node-drag-end="allowDrag" @node-click="nodeClick" v-if="!loading" draggable>
:allow-drop="allowDrop" @node-drag-end="allowDrag" @node-click="nodeClick" v-if="!loading" draggable class="ms-is-leaf">
<span class="custom-tree-node father" slot-scope="{ node, data}" style="width: 96%">
<!-- 步骤组件-->
<ms-component-config :type="data.type" :scenario="data" :response="response" :currentScenario="currentScenario"
@ -873,6 +873,7 @@
}
this.enableCookieShare = obj.enableCookieShare;
this.scenarioDefinition = obj.hashTree;
console.log(this.scenarioDefinition)
}
}
if (this.currentScenario.copy) {
@ -1035,7 +1036,7 @@
color: #7C3985;
}
/deep/ .is-leaf {
.ms-is-leaf >>> .is-leaf {
color: transparent;
}
</style>

View File

@ -24,10 +24,17 @@
</span>
<div class="header-right" @click.stop>
<el-switch v-model="data.enable" class="enable-switch"/>
<slot name="message"></slot>
<el-tooltip :content="$t('test_resource_pool.enable_disable')" placement="top">
<el-switch v-model="data.enable" class="enable-switch"/>
</el-tooltip>
<slot name="button"></slot>
<el-button size="mini" icon="el-icon-copy-document" circle @click="copyRow"/>
<el-button size="mini" icon="el-icon-delete" type="danger" circle @click="remove"/>
<el-tooltip content="Copy" placement="top">
<el-button size="mini" icon="el-icon-copy-document" circle @click="copyRow"/>
</el-tooltip>
<el-tooltip :content="$t('commons.remove')" placement="top">
<el-button size="mini" icon="el-icon-delete" type="danger" circle @click="remove"/>
</el-tooltip>
</div>
</div>

View File

@ -20,7 +20,9 @@
</template>
<template v-slot:button>
<el-button @click="run" :tip="$t('api_test.run')" icon="el-icon-video-play" style="background-color: #409EFF;color: white;" size="mini" circle/>
<el-tooltip :content="$t('api_test.run')" placement="top">
<el-button @click="run" icon="el-icon-video-play" style="background-color: #409EFF;color: white;" size="mini" circle/>
</el-tooltip>
</template>
<div v-if="request.protocol === 'HTTP'">
@ -36,13 +38,13 @@
</el-input>
</div>
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<ms-api-request-form :referenced="true" :headers="request.headers " :request="request" v-if="request.protocol==='HTTP' || request.type==='HTTPSamplerProxy'"/>
<ms-api-request-form :isShowEnable="true" :referenced="true" :headers="request.headers " :request="request" v-if="request.protocol==='HTTP' || request.type==='HTTPSamplerProxy'"/>
<ms-tcp-basis-parameters :request="request" v-if="request.protocol==='TCP'|| request.type==='TCPSampler'"/>
<ms-sql-basis-parameters :request="request" v-if="request.protocol==='SQL'|| request.type==='JDBCSampler'" :showScript="false"/>
<ms-dubbo-basis-parameters :request="request" v-if="request.protocol==='DUBBO' || request.protocol==='dubbo://'|| request.type==='DubboSampler'" :showScript="false"/>
<p class="tip">{{$t('api_test.definition.request.res_param')}} </p>
<api-response-component :result="request.requestResult"/>
<api-response-component :currentProtocol="request.protocol" :result="request.requestResult"/>
<!-- 保存操作 -->
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)" v-if="!request.referenced">
@ -86,7 +88,7 @@
reqOptions: REQ_METHOD,
reportId: "",
runData: [],
isShowInput: false
isShowInput: false,
}
},
created() {

View File

@ -8,7 +8,7 @@
<el-collapse-transition>
<div v-if="isActive">
<el-divider></el-divider>
<ms-request-result-tail :show-metric="false" :response="response"/>
<ms-request-result-tail :currentProtocol="currentProtocol" :show-metric="false" :response="response"/>
</div>
</el-collapse-transition>
</el-card>
@ -24,7 +24,7 @@
export default {
name: "ApiResponseComponent",
components: {ElCollapseTransition, MsRequestResultTail, ApiBaseComponent, MsRequestMetric},
props: {apiItem: {}, result: {}},
props: {apiItem: {}, result: {}, currentProtocol: String},
data() {
return {
isActive: false,

View File

@ -31,7 +31,7 @@
},
data() {
return {
title: this.$t('api_test.automation.customize_script'),
title: "",
titleColor: "",
backgroundColor: "",
}

View File

@ -1,85 +1,112 @@
<template>
<div>
<ms-run :debug="true" :environment="currentEnvironmentId" :reportId="reportId" :run-data="debugData"
@runRefresh="runRefresh" ref="runTest"/>
<api-base-component
@copy="copyRow"
@remove="remove"
:data="controller"
:draggable="true"
color="#02A7F0"
background-color="#F4F4F5"
:title="$t('api_test.automation.loop_controller')" v-loading="loading">
<api-base-component
@copy="copyRow"
@remove="remove"
:data="controller"
:draggable="true"
color="#02A7F0"
background-color="#F4F4F5"
:title="$t('api_test.automation.loop_controller')">
<template v-slot:headerLeft>
<i class="icon el-icon-arrow-right" :class="{'is-active': controller.active}" @click="active(controller)" style="margin-right: 10px"/>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="LOOP_COUNT">{{$t('loop.loops_title')}}</el-radio>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="FOREACH">{{$t('loop.foreach')}}</el-radio>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="WHILE">{{$t('loop.while')}}</el-radio>
</template>
<template v-slot:headerLeft>
<i class="icon el-icon-arrow-right" :class="{'is-active': controller.active}" @click="active(controller)" style="margin-right: 10px"/>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="LOOP_COUNT">{{$t('loop.loops_title')}}</el-radio>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="FOREACH">{{$t('loop.foreach')}}</el-radio>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="WHILE">{{$t('loop.while')}}</el-radio>
</template>
<template v-slot:message>
<span v-if="requestResult && requestResult.scenarios && requestResult.scenarios.length > 0 " style="color: #8c939d;margin-right: 10px">
循环{{requestResult.scenarios.length}} 成功{{success}} 失败{{error}}
</span>
</template>
<div v-if="controller.loopType==='LOOP_COUNT'" draggable>
<el-row>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.loops')}}</span>
<el-input-number size="small" v-model="controller.countController.loops" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.countController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.proceed')}}</span>
<el-tooltip class="item" effect="dark" content="默认为开启,当循环下只有一个请求时,可以开启/关闭;当循环下超过一个请求时,则只能开启。" placement="top">>
<el-switch v-model="controller.countController.proceed" @change="switchChange"/>
<template v-slot:button>
<el-button @click="runDebug" :tip="$t('api_test.run')" icon="el-icon-video-play" style="background-color: #409EFF;color: white;" size="mini" circle/>
</template>
<div v-if="controller.loopType==='LOOP_COUNT'" draggable>
<el-row>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.loops')}}</span>
<el-input-number size="small" v-model="controller.countController.loops" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.countController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.proceed')}}</span>
<el-tooltip class="item" effect="dark" content="默认为开启,当循环下只有一个请求时,可以开启/关闭;当循环下超过一个请求时,则只能开启。" placement="top">>
<el-switch v-model="controller.countController.proceed" @change="switchChange"/>
</el-tooltip>
</el-col>
</el-row>
</div>
</el-tooltip>
</el-col>
</el-row>
</div>
<div v-else-if="controller.loopType==='FOREACH'" draggable>
<el-row>
<el-col :span="8">
<el-input :placeholder="$t('api_test.request.condition_variable')" v-model="controller.forEachController.inputVal" size="small"/>
</el-col>
<el-col :span="1" style="margin-top: 6px">
<span style="margin:10px 10px 10px">in</span>
</el-col>
<el-col :span="8">
<el-input :placeholder="$t('api_test.request.condition_variable')" v-model="controller.forEachController.returnVal" size="small"/>
</el-col>
<el-col :span="7">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.forEachController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</el-col>
</el-row>
</div>
<div v-else draggable>
<el-input size="small" v-model="controller.whileController.variable" style="width: 20%" :placeholder="$t('api_test.request.condition_variable')"/>
<div v-else-if="controller.loopType==='FOREACH'" draggable>
<el-row>
<el-col :span="8">
<el-input placeholder="输出变量名称" v-model="controller.forEachController.returnVal" size="small"/>
</el-col>
<el-col :span="1" style="margin-top: 6px">
<span style="margin:10px 10px 10px">in</span>
</el-col>
<el-col :span="8">
<el-input placeholder="输入变量前缀" v-model="controller.forEachController.inputVal" size="small"/>
</el-col>
<el-col :span="7">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.forEachController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</el-col>
</el-row>
</div>
<div v-else draggable>
<el-input size="small" v-model="controller.whileController.variable" style="width: 20%" :placeholder="$t('api_test.request.condition_variable')"/>
<el-select v-model="controller.whileController.operator" :placeholder="$t('commons.please_select')" size="small"
@change="change" style="width: 10%;margin-left: 10px">
<el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/>
</el-select>
<el-input size="small" v-model="controller.whileController.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/>
<span class="ms-span ms-radio">{{$t('loop.timeout')}}</span>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="1" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</div>
<el-select v-model="controller.whileController.operator" :placeholder="$t('commons.please_select')" size="small"
@change="change" style="width: 10%;margin-left: 10px">
<el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/>
</el-select>
<el-input size="small" v-model="controller.whileController.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/>
<span class="ms-span ms-radio">{{$t('loop.timeout')}}</span>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="1" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</div>
</api-base-component>
<p class="tip">{{$t('api_test.definition.request.res_param')}} </p>
<el-tabs v-model="activeName">
<el-tab-pane :label="item.name" :name="item.name" v-for="(item,index) in requestResult.scenarios" :key="index">
<div v-for="(result,i) in item.requestResults" :key="i" style="margin-bottom: 5px">
<api-response-component :result="result"/>
</div>
</el-tab-pane>
</el-tabs>
</api-base-component>
</div>
</template>
<script>
import ApiBaseComponent from "../common/ApiBaseComponent";
import ApiResponseComponent from "./ApiResponseComponent";
import MsRun from "../DebugRun";
import {getUUID, getCurrentProjectID} from "@/common/js/utils";
export default {
name: "MsLoopController",
components: {ApiBaseComponent},
components: {ApiBaseComponent, ApiResponseComponent, MsRun},
props: {
controller: {},
currentEnvironmentId: String,
currentScenario: {},
node: {},
index: Object,
draggable: {
@ -87,9 +114,19 @@
default: false,
},
},
created() {
this.initResult();
},
data() {
return {
loading: false,
activeName: "first",
requestResult: {responseResult: {}},
success: 0,
error: 0,
debugData: {},
report: [],
reportId: "",
operators: {
EQ: {
label: "commons.adv_search.operators.equals",
@ -127,6 +164,25 @@
}
},
methods: {
initResult() {
if (this.controller) {
switch (this.controller.loopType) {
case "LOOP_COUNT":
this.requestResult = this.controller.countController && this.controller.countController.requestResult ? this.controller.countController.requestResult : {};
break;
case "FOREACH":
this.requestResult = this.controller.forEachController && this.controller.forEachController.requestResult ? this.controller.forEachController.requestResult : {};
break;
case "WHILE":
this.requestResult = this.controller.whileController && this.controller.whileController.requestResult ? this.controller.whileController.requestResult : {};
break;
default:
break;
}
}
this.getFails();
this.activeName = this.requestResult && this.requestResult.scenarios && this.requestResult.scenarios.length > 0 ? this.requestResult.scenarios[0].name : "";
},
switchChange() {
if (this.controller.hashTree && this.controller.hashTree.length > 1) {
this.$warning("当前循环下超过一个请求,不能关闭状态")
@ -134,6 +190,26 @@
return;
}
},
runDebug() {
/*触发执行操作*/
if (!this.currentEnvironmentId) {
this.$error(this.$t('api_test.environment.select_environment'));
return;
}
if (!this.controller.hashTree || this.controller.hashTree.length < 1) {
this.$warning("当前循环下没有请求,不能执行")
return;
}
this.controller.active = true;
this.loading = true;
this.debugData = {
id: this.currentScenario.id, name: this.currentScenario.name, type: "scenario",
variables: this.currentScenario.variables, referenced: 'Created', enableCookieShare: this.enableCookieShare,
environmentId: this.currentEnvironmentId, hashTree: [this.controller]
};
this.reportId = getUUID().substring(0, 8);
},
remove() {
this.$emit('remove', this.controller, this.node);
},
@ -146,6 +222,7 @@
},
changeRadio() {
this.controller.active = true;
this.initResult();
this.reload();
},
change(value) {
@ -159,11 +236,77 @@
this.loading = false
})
},
runRefresh() {
this.getReport();
},
getFails() {
this.error = 0;
this.success = 0;
if (this.requestResult.scenarios) {
this.requestResult.scenarios.forEach((scenario) => {
if (scenario.requestResults) {
scenario.requestResults.forEach(item => {
if (item.error > 0) {
this.error++;
return;
}
})
}
})
this.success = this.requestResult.scenarios.length - this.error;
}
},
getReport() {
if (this.reportId) {
let url = "/api/scenario/report/get/" + this.reportId;
this.$get(url, response => {
this.report = response.data || {};
if (response.data) {
if (this.isNotRunning) {
try {
this.requestResult = JSON.parse(this.report.content);
this.controller.requestResult = this.requestResult;
switch (this.controller.loopType) {
case "LOOP_COUNT":
this.controller.countController.requestResult = this.requestResult;
break;
case "FOREACH":
this.controller.forEachController.requestResult = this.requestResult;
break;
case "WHILE":
this.controller.whileController.requestResult = this.requestResult;
break;
default:
break;
}
this.getFails();
if (!this.requestResult) {
this.requestResult = {scenarios: []};
}
} catch (e) {
throw e;
}
this.loading = false;
this.activeName = this.requestResult && this.requestResult.scenarios ? this.requestResult.scenarios[0].name : "";
} else {
setTimeout(this.getReport, 2000)
}
} else {
this.loading = false;
this.$error(this.$t('api_report.not_exist'));
}
});
}
},
},
computed: {
hasEmptyOperator() {
return !!this.controller.operator && this.controller.operator.indexOf("empty") > 0;
},
isNotRunning() {
return "Running" !== this.report.status;
}
}
}
</script>
@ -180,6 +323,14 @@
font-weight: normal;
}
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
margin: 20px 0;
}
.icon.is-active {
transform: rotate(90deg);
}

View File

@ -29,7 +29,7 @@
<el-col :span="20">
<el-autocomplete
size="small"
class="inline-input"
style="width: 100%"
v-model="editData.encoding"
:fetch-suggestions="querySearch"
:placeholder="$t('commons.input_content')"
@ -110,7 +110,7 @@
},
handleClick() {
let config = {complete: this.complete, step: this.step};
let config = {complete: this.complete, step: this.step, delimiter: this.editData.delimiter ? this.editData.delimiter : ","};
this.allDatas = [];
//
if (this.editData.files && this.editData.files.length > 0 && this.editData.files[0].file) {
@ -152,5 +152,7 @@
</script>
<style scoped>
ms-is-leaf >>> .is-leaf {
color: red;
}
</style>

View File

@ -107,6 +107,9 @@
},
addParameters(v) {
v.id = getUUID();
if (v.type === 'CSV') {
v.delimiter = ",";
}
this.variables.push(v);
let index = 1;
this.variables.forEach(item => {

View File

@ -1,53 +1,50 @@
<template>
<div>
<div v-loading="loading">
<span class="kv-description" v-if="description">
{{ description }}
</span>
<ms-draggable element="ul" @update="endChange"
v-model="keyValues" v-bind="{draggable:'.item'}">
<div class="kv-row item" v-for="(item, index) in keyValues" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-button icon="el-icon-sort" circle size="mini"/>
<div class="kv-row item" v-for="(item, index) in items" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-col class="kv-checkbox" v-if="isShowEnable">
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
:disabled="isReadOnly"/>
<el-col class="kv-checkbox" v-if="isShowEnable">
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
:disabled="isReadOnly"/>
</el-col>
</el-col>
<span style="margin-left: 10px" v-else></span>
<el-col class="item">
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
@change="change"
:placeholder="keyText" show-word-limit/>
<el-autocomplete :disabled="isReadOnly" :maxlength="200" v-if="suggestions" v-model="item.name" size="small"
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText"
show-word-limit/>
<i class="el-icon-top" style="cursor:pointer" @click="moveTop(index)"/>
<i class="el-icon-bottom" style="cursor:pointer;" @click="moveBottom(index)"/>
</el-col>
<el-col class="item">
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
@change="change"
:placeholder="keyText" show-word-limit/>
<el-autocomplete :disabled="isReadOnly" :maxlength="200" v-if="suggestions" v-model="item.name" size="small"
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText"
show-word-limit/>
<el-col class="item">
<el-input :disabled="isReadOnly" v-model="item.value" size="small" @change="change"
:placeholder="valueText" show-word-limit/>
</el-col>
<el-col class="item kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index) || isReadOnly"/>
</el-col>
</el-row>
</div>
</ms-draggable>
</el-col>
<el-col class="item">
<el-input :disabled="isReadOnly" v-model="item.value" size="small" @change="change"
:placeholder="valueText" show-word-limit/>
</el-col>
<el-col class="item kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index) || isReadOnly"/>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import {KeyValue} from "../model/ApiTestModel";
import MsDraggable from 'vuedraggable'
import Vue from 'vue';
export default {
name: "MsApiKeyValue",
components: {
MsDraggable
},
props: {
keyPlaceholder: String,
valuePlaceholder: String,
@ -66,6 +63,7 @@
data() {
return {
keyValues: [],
loading: false,
}
},
computed: {
@ -78,6 +76,31 @@
},
methods: {
moveBottom(index) {
if (this.items.length < 2 || index === this.items.length - 2) {
return;
}
let thisRow = this.items[index];
let nextRow = this.items[index + 1];
Vue.set(this.items, index + 1, thisRow);
Vue.set(this.items, index, nextRow)
},
moveTop(index) {
if (index === 0) {
return;
}
let thisRow = this.items[index];
let lastRow = this.items[index - 1];
Vue.set(this.items, index - 1, thisRow);
Vue.set(this.items, index, lastRow)
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
remove: function (index) {
//
this.items.splice(index, 1);
@ -115,27 +138,11 @@
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
};
},
endChange(env) {
if (env.newIndex == env.oldIndex) {
return;
}
let newItem = this.keyValues[env.newIndex];
let oldItem = this.keyValues[env.oldIndex];
this.$set(this.keyValues, env.oldIndex, oldItem);
this.$set(this.keyValues, env.newIndex, newItem)
this.items.forEach(item => {
this.items.splice(0);
})
this.keyValues.forEach(item => {
this.items.push(item);
})
}
},
created() {
if (this.items.length === 0 || this.items[this.items.length - 1].name) {
this.items.push(new KeyValue({enable: true}));
}
this.keyValues = this.items;
}
}
</script>
@ -161,4 +168,8 @@
.el-autocomplete {
width: 100%;
}
i:hover {
color: #783887;
}
</style>

View File

@ -3,85 +3,83 @@
<span class="kv-description" v-if="description">
{{ description }}
</span>
<ms-draggable element="ul" @update="endChange"
v-model="keyValues" v-bind="{draggable:'.item'}">
<div class="item kv-row" v-for="(item, index) in keyValues" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-button icon="el-icon-sort" circle size="mini"/>
<div class="item kv-row" v-for="(item, index) in parameters" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-col class="kv-checkbox" v-if="isShowEnable">
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
:disabled="isReadOnly"/>
</el-col>
<span style="margin-left: 10px" v-else></span>
<i class="el-icon-top" style="cursor:pointer" @click="moveTop(index)"/>
<i class="el-icon-bottom" style="cursor:pointer;" @click="moveBottom(index)"/>
<el-col class="kv-checkbox" v-if="isShowEnable">
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
:disabled="isReadOnly"/>
</el-col>
<el-col class="item">
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
@change="change" :placeholder="keyText" show-word-limit>
<template v-slot:prepend>
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type"
@change="typeChange(item)">
<el-option value="text"/>
<el-option value="file"/>
</el-select>
</template>
</el-input>
<el-col class="item">
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
@change="change" :placeholder="keyText" show-word-limit>
<template v-slot:prepend>
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type"
@change="typeChange(item)">
<el-option value="text"/>
<el-option value="file"/>
</el-select>
</template>
</el-input>
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
</el-col>
</el-col>
<el-col class="item kv-select">
<el-select v-model="item.required" size="small">
<el-option v-for="req in requireds" :key="req.id" :label="req.name" :value="req.id"/>
</el-select>
</el-col>
<el-col class="item kv-select">
<el-select v-model="item.required" size="small">
<el-option v-for="req in requireds" :key="req.id" :label="req.name" :value="req.id"/>
</el-select>
</el-col>
<el-col class="item" v-if="item.type !== 'file'">
<el-autocomplete
:disabled="isReadOnly"
size="small"
class="input-with-autocomplete"
v-model="item.value"
:fetch-suggestions="funcSearch"
:placeholder="valueText"
value-key="name"
highlight-first-item
@select="change">
<i slot="suffix" class="el-input__icon el-icon-edit pointer" @click="advanced(item)"></i>
</el-autocomplete>
</el-col>
<el-col class="item" v-if="item.type !== 'file'">
<el-autocomplete
:disabled="isReadOnly"
size="small"
class="input-with-autocomplete"
v-model="item.value"
:fetch-suggestions="funcSearch"
:placeholder="valueText"
value-key="name"
highlight-first-item
@select="change">
<i slot="suffix" class="el-input__icon el-icon-edit pointer" @click="advanced(item)"></i>
</el-autocomplete>
</el-col>
<el-col class="item">
<el-input v-model="item.description" size="small" maxlength="200"
:placeholder="$t('commons.description')" show-word-limit>
</el-input>
<el-col class="item">
<el-input v-model="item.description" size="small" maxlength="200"
:placeholder="$t('commons.description')" show-word-limit>
</el-input>
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
</el-col>
</el-col>
<el-col v-if="item.type === 'file'" class="item">
<ms-api-body-file-upload :parameter="item"/>
</el-col>
<el-col v-if="item.type === 'file'" class="item">
<ms-api-body-file-upload :parameter="item"/>
</el-col>
<el-col v-if="type === 'body'" class="item kv-select">
<el-input :disabled="isReadOnly" v-model="item.contentType" size="small"
@change="change" :placeholder="$t('api_test.request.content_type')" show-word-limit>
</el-input>
</el-col>
<el-col v-if="type === 'body'" class="item kv-select">
<el-input :disabled="isReadOnly" v-model="item.contentType" size="small"
@change="change" :placeholder="$t('api_test.request.content_type')" show-word-limit>
</el-input>
</el-col>
<el-col class="item kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index) || isReadOnly"/>
</el-col>
<el-col class="item kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index) || isReadOnly"/>
</el-col>
</el-row>
</div>
</ms-draggable>
</el-row>
</div>
<ms-api-variable-advance ref="variableAdvance" :environment="environment" :scenario="scenario"
:parameters="parameters"
:current-item="currentItem"/>
@ -94,11 +92,11 @@
import MsApiVariableAdvance from "./ApiVariableAdvance";
import MsApiBodyFileUpload from "./body/ApiBodyFileUpload";
import {REQUIRED} from "../model/JsonData";
import MsDraggable from 'vuedraggable'
import Vue from 'vue';
export default {
name: "MsApiVariable",
components: {MsApiBodyFileUpload, MsApiVariableAdvance, MsDraggable},
components: {MsApiBodyFileUpload, MsApiVariableAdvance},
props: {
keyPlaceholder: String,
valuePlaceholder: String,
@ -125,7 +123,6 @@
return {
currentItem: null,
requireds: REQUIRED,
keyValues: [],
}
},
computed: {
@ -137,6 +134,25 @@
}
},
methods: {
moveBottom(index) {
if (this.parameters.length < 2 || index === this.parameters.length - 2) {
return;
}
let thisRow = this.parameters[index];
let nextRow = this.parameters[index + 1];
Vue.set(this.parameters, index + 1, thisRow);
Vue.set(this.parameters, index, nextRow)
},
moveTop(index) {
if (index === 0) {
return;
}
let thisRow = this.parameters[index];
let lastRow = this.parameters[index - 1];
Vue.set(this.parameters, index - 1, thisRow);
Vue.set(this.parameters, index, lastRow)
},
remove: function (index) {
//
this.parameters.splice(index, 1);
@ -204,21 +220,6 @@
item.contentType = 'text/plain';
}
},
endChange(env) {
if (env.newIndex == env.oldIndex) {
return;
}
let newItem = this.keyValues[env.newIndex];
let oldItem = this.keyValues[env.oldIndex];
this.$set(this.keyValues, env.oldIndex, oldItem);
this.$set(this.keyValues, env.newIndex, newItem)
this.parameters.forEach(item => {
this.parameters.splice(0);
})
this.keyValues.forEach(item => {
this.parameters.push(item);
})
}
},
created() {
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
@ -230,7 +231,6 @@
contentType: 'text/plain'
}));
}
this.keyValues = this.parameters;
}
}
</script>

View File

@ -85,7 +85,7 @@
<p class="tip">{{ $t('api_test.definition.request.req_param') }} </p>
<ms-api-request-form :showScript="true" :is-read-only="isReadOnly" :headers="apiCase.request.headers " :request="apiCase.request" v-if="api.protocol==='HTTP'"/>
<ms-api-request-form :isShowEnable="true" :showScript="true" :is-read-only="isReadOnly" :headers="apiCase.request.headers " :request="apiCase.request" v-if="api.protocol==='HTTP'"/>
<ms-tcp-basis-parameters :showScript="true" :request="apiCase.request" v-if="api.protocol==='TCP'"/>
<ms-sql-basis-parameters :showScript="true" :request="apiCase.request" v-if="api.protocol==='SQL'"/>
<ms-dubbo-basis-parameters :showScript="true" :request="apiCase.request" v-if="api.protocol==='DUBBO'"/>

View File

@ -26,7 +26,7 @@
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<!-- HTTP 请求参数 -->
<ms-api-request-form :headers="request.headers" :request="request" :response="responseData"/>
<ms-api-request-form :isShowEnable="true" :headers="request.headers" :request="request" :response="responseData"/>
</el-form>
<!-- HTTP 请求返回数据 -->
<p class="tip">{{$t('api_test.definition.request.res_param')}} </p>

View File

@ -49,8 +49,13 @@
},
methods: {
validateSocket(socket) {
// if (!socket) return true;
if (socket !== ''){
if (!socket) {
this.httpConfig.domain = socket;
this.httpConfig.port = '';
this.httpConfig.socket = socket;
return true;
}
let urlStr = this.httpConfig.protocol + '://' + socket;
let url = {};
try {
@ -67,13 +72,6 @@
this.httpConfig.socket = this.httpConfig.domain + path;
}
return true;
}else {
this.httpConfig.domain = socket;
this.httpConfig.port = '';
this.httpConfig.socket = socket;
return true;
}
},
validate() {
let isValidate = false;

View File

@ -317,6 +317,15 @@
case 'coverage':
this.condition.apiCaseCoverage = 'coverage';
break;
case 'Prepare':
this.condition.filters.status = [this.selectDataRange];
break;
case 'Completed':
this.condition.filters.status = [this.selectDataRange];
break;
case 'Underway':
this.condition.filters.status = [this.selectDataRange];
break;
}
if (this.condition.projectId) {
this.result = this.$post("/api/definition/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => {

View File

@ -3,7 +3,7 @@
<ms-tag v-if="value === 'Prepare'" type="info" effect="plain" :content="$t('test_track.plan.plan_status_prepare')"/>
<ms-tag v-if="value === 'Underway'" type="warning" effect="plain" :content="$t('test_track.plan.plan_status_running')"/>
<ms-tag v-if="value === 'Completed'" type="success" effect="plain" :content="$t('test_track.plan.plan_status_completed')"/>
<ms-tag v-if="value === 'Trash'" type="danger" effect="plain" content="废弃"/>
<ms-tag v-if="value === 'Trash'" type="danger" effect="plain" :content="$t('test_track.plan.plan_status_trash')"/>
</span>
</template>

View File

@ -43,7 +43,7 @@
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<!-- HTTP 请求参数 -->
<ms-api-request-form :headers="api.request.headers" :request="api.request"/>
<ms-api-request-form :isShowEnable="true" :headers="api.request.headers" :request="api.request"/>
</el-form>
<!--返回结果-->

View File

@ -1024,9 +1024,9 @@ export class LoopController extends Controller {
this.type = "LoopController";
this.active = false;
this.loopType = "LOOP_COUNT";
this.countController = {loops: 0, interval: 0, proceed: true};
this.forEachController = {inputVal: "", returnVal: "", interval: 0};
this.whileController = {variable: "", operator: "", value: "", timeout: 0};
this.countController = {loops: 0, interval: 0, proceed: true, requestResult: {}};
this.forEachController = {inputVal: "", returnVal: "", interval: 0, requestResult: {}};
this.whileController = {variable: "", operator: "", value: "", timeout: 0, requestResult: {}};
this.hashTree = [];
this.set(options);
}

View File

@ -64,11 +64,11 @@ export class HttpConfig extends BaseConfig {
constructor(options = {}) {
super();
this.socket = undefined;
this.domain = undefined;
this.socket = '';
this.domain = '';
this.headers = [];
this.protocol = 'https';
this.port = undefined;
this.port = '';
this.set(options);
this.sets({headers: KeyValue}, options);

View File

@ -93,11 +93,13 @@ export default {
],
isCodeEditAlive: true,
languages: [
'beanshell', "python"
'beanshell', "python", "groovy", "javascript"
],
codeEditModeMap: {
beanshell: 'java',
python: 'python'
python: 'python',
groovy: 'java',
javascript: 'javascript',
}
}
},

View File

@ -65,7 +65,7 @@
import MsDubboRegistryCenter from "@/business/components/api/test/components/request/dubbo/RegistryCenter";
import MsDubboConfigCenter from "@/business/components/api/test/components/request/dubbo/ConfigCenter";
import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor";
import MsJsr233Processor from "../processor/Jsr233Processor";
export default {
name: "MsApiDubboRequestForm",

View File

@ -94,7 +94,7 @@ import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "@/business/components/api/test/components/ApiVariable";
import MsApiAdvancedConfig from "../ApiAdvancedConfig";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor";
import MsJsr233Processor from "../processor/Jsr233Processor";
export default {
name: "MsApiHttpRequestForm",

View File

@ -78,7 +78,7 @@
import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService";
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsApiScenarioVariables from "../ApiScenarioVariables";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor";
import MsJsr233Processor from "../processor/Jsr233Processor";
export default {
name: "MsApiSqlRequestForm",

View File

@ -120,7 +120,7 @@ import {Scenario, TCPConfig, TCPRequest} from "@/business/components/api/test/mo
import MsApiAssertions from "@/business/components/api/test/components/assertion/ApiAssertions";
import MsApiExtract from "@/business/components/api/test/components/extract/ApiExtract";
import MsCodeEdit from "@/business/components/common/components/MsCodeEdit";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor";
import MsJsr233Processor from "../processor/Jsr233Processor";
export default {
name: "MsApiTcpRequestForm",

View File

@ -9,7 +9,9 @@
<el-row>
<el-col :span="4">
<span>
{{operationType == 'edit' ? ( readOnly ? $t('test_track.case.view_case') : $t('test_track.case.edit_case')) : $t('test_track.case.create')}}
{{
operationType == 'edit' ? (readOnly ? $t('test_track.case.view_case') : $t('test_track.case.edit_case')) : $t('test_track.case.create')
}}
</span>
</el-col>
<el-col class="head-right" :span="19">
@ -82,7 +84,7 @@
<el-row>
<el-col :span="10" :offset="1">
<el-form-item :label="$t('commons.tag')" :label-width="formLabelWidth" prop="tag">
<ms-input-tag :currentScenario="form" ref="tag"/>
<ms-input-tag :currentScenario="form" v-if="showInputTag" ref="tag"/>
</el-form-item>
</el-col>
</el-row>
@ -318,6 +320,7 @@ export default {
result: ''
}],
remark: '',
tags: [],
},
moduleOptions: [],
maintainerOptions: [],
@ -350,7 +353,8 @@ export default {
],
testCase: {},
testCases: [],
index: 0
index: 0,
showInputTag: true,
};
},
props: {
@ -416,6 +420,7 @@ export default {
this.form.type = 'functional';
this.form.method = 'manual';
this.form.maintainer = user.id;
this.form.tags = [];
this.getSelectOptions();
this.reload();
}
@ -441,6 +446,7 @@ export default {
});
},
getTestCase(index) {
this.showInputTag = false;
let testCase = this.testCases[index];
this.result = this.$get('/test/case/get/' + testCase.id, response => {
let testCase = response.data;
@ -448,6 +454,9 @@ export default {
this.setTestCaseExtInfo(testCase);
this.getSelectOptions();
this.reload();
this.$nextTick(() => {
this.showInputTag = true
})
})
},
setFormData(testCase) {
@ -459,7 +468,7 @@ export default {
this.form.module = testCase.nodeId;
this.getFileMetaData(testCase);
},
setTestCaseExtInfo (testCase) {
setTestCaseExtInfo(testCase) {
this.testCase = {};
if (testCase) {
//

View File

@ -137,12 +137,6 @@ export default {
return false;
},
filter(val) {
if (!val) {
val = this.filterText;
} else {
// condition filterText
this.filterText = val;
}
this.$nextTick(() => {
this.$refs.tree.filter(val);
});

View File

@ -3,12 +3,14 @@
<ms-tag v-if="value == 'Prepare'" type="info" :content="$t('test_track.plan.plan_status_prepare')"/>
<ms-tag v-if="value == 'Underway'" type="primary" :content="$t('test_track.plan.plan_status_running')"/>
<ms-tag v-if="value == 'Completed'" type="success" :content="$t('test_track.plan.plan_status_completed')"/>
<ms-tag v-if="value === 'Trash'" type="danger" effect="plain" :content="$t('test_track.plan.plan_status_trash')"/>
</div>
</template>
<script>
import MsTag from "../../../../common/components/MsTag";
export default {
import MsTag from "../../../../common/components/MsTag";
export default {
name: "PlanStatusTableItem",
components: {MsTag},
props: {

View File

@ -75,6 +75,9 @@
<el-tag size="mini" type="success" v-else-if="row.caseStatus === 'success'">
{{ row.caseStatus }}
</el-tag>
<el-tag size="mini" v-else-if="row.caseStatus === 'run'">
{{ row.caseStatus }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
@ -264,17 +267,22 @@ export default {
title: loadCase.caseName,
message: this.$t('test_track.plan.load_case.exec').toString()
});
this.initTable();
this.updateStatus(loadCase, 'run');
}).catch(() => {
this.$post('/test/plan/load/case/update', {id: loadCase.id, status: "error"}, () => {
this.initTable();
});
this.updateStatus(loadCase, 'error');
this.$notify.error({
title: loadCase.caseName,
message: this.$t('test_track.plan.load_case.error').toString()
});
})
},
updateStatus(loadCase, status) {
this.$post('/test/plan/load/case/update', {id: loadCase.id, status: status}, () => {
this.$post('/test/plan/edit/status/' + loadCase.testPlanId, {},() => {
this.initTable();
});
});
},
handleDelete(loadCase) {
this.result = this.$get('/test/plan/load/case/delete/' + loadCase.id, () => {
this.$success(this.$t('test_track.cancel_relevance_success'));
@ -284,15 +292,15 @@ export default {
},
sort(column) {
//
// if (this.condition.orders) {
// this.condition.orders = [];
// }
// _sort(column, this.condition);
// this.initTable();
if (this.condition.orders) {
this.condition.orders = [];
}
_sort(column, this.condition);
this.initTable();
},
filter(filters) {
// _filter(filters, this.condition);
// this.initTable();
_filter(filters, this.condition);
this.initTable();
},
getReport(data) {
const {loadReportId} = data;

View File

@ -5,8 +5,25 @@
@search="initTableData"
:title="$t('test_track.report.name')"/>
</template>
<el-table border class="adjust-table" :data="tableData"
@filter-change="filter" @sort-change="sort">
<el-table border :data="tableData"
@select-all="handleSelectAll"
@select="handleSelect"
row-key="id" class="test-content adjust-table ms-select-all"
@filter-change="filter" @sort-change="sort" ref="testPlanReportTable">
<el-table-column width="50" type="selection"/>
<ms-table-select-all
:page-size="pageSize>total?total:pageSize"
:total="total"
@selectPageAll="isSelectDataAll(false)"
@selectAll="isSelectDataAll(true)"/>
<el-table-column width="30" :resizable="false" align="center">
<template v-slot:default="scope">
<show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectDataCounts"/>
</template>
</el-table-column>
<el-table-column min-width="300" prop="name" :label="$t('test_track.report.list.name')" show-overflow-tooltip></el-table-column>
<el-table-column prop="testPlanName" sortable :label="$t('test_track.report.list.test_plan')" show-overflow-tooltip></el-table-column>
<el-table-column prop="creator" :label="$t('test_track.report.list.creator')" show-overflow-tooltip></el-table-column>
@ -20,6 +37,13 @@
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="scope">
<ms-tag v-if="scope.row.status == 'RUNNING'" type="success" effect="plain" :content="'Running'"/>
<ms-tag v-else-if="scope.row.status == 'COMPLETED'||scope.row.status == 'SUCCESS'||scope.row.status == 'FAILED'" type="info" effect="plain" :content="'Completed'"/>
<ms-tag v-else type="effect" effect="plain" :content="scope.row.status"/>
</template>
</el-table-column>
<el-table-column min-width="150" :label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator-button :tip="$t('test_track.plan_view.view_report')" icon="el-icon-document"
@ -45,13 +69,17 @@ import {TEST_PLAN_REPORT_CONFIGS} from "../../../common/components/search/search
import {getCurrentProjectID} from "../../../../../common/js/utils";
import TestPlanReportView from "@/business/components/track/report/components/TestPlanReportView";
import ReportTriggerModeItem from "@/business/components/common/tableItem/ReportTriggerModeItem";
import MsTag from "@/business/components/common/components/MsTag";
import ShowMoreBtn from "@/business/components/track/case/components/ShowMoreBtn";
import MsTableSelectAll from "@/business/components/common/components/table/MsTableSelectAll";
export default {
name: "TestPlanReportList",
components: {
TestPlanReportView,
MsTableOperator, MsTableOperatorButton, MsTableHeader, MsTablePagination,
ReportTriggerModeItem
ReportTriggerModeItem,MsTag,
ShowMoreBtn,MsTableSelectAll,
},
data() {
return {
@ -64,6 +92,7 @@ export default {
currentPage: 1,
pageSize: 10,
isTestManagerOrTestUser: false,
selectRows: new Set(),
total: 0,
tableData: [],
statusFilters: [
@ -76,13 +105,16 @@ export default {
{text: this.$t('test_track.plan.system_test'), value: 'system'},
{text: this.$t('test_track.plan.regression_test'), value: 'regression'},
],
buttons: [
{name: this.$t('api_test.definition.request.batch_delete'), handleClick: this.handleDeleteBatch},
],
selectAll: false,
unSelection: [],
selectDataCounts: 0,
}
},
watch: {
'$route'(to, from) {
// if (to.path.indexOf("/track/plan/all") >= 0) {
// this.initTableData();
// }
}
},
activated() {
@ -95,6 +127,10 @@ export default {
},
methods: {
initTableData() {
this.selectRows = new Set();
this.selectAll = false;
this.unSelection = [];
this.selectDataCounts = 0;
if (this.planId) {
this.condition.planId = this.planId;
}
@ -108,19 +144,52 @@ export default {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
// for (let i = 0; i < this.tableData.length; i++) {
// let path = "/test/plan/project";
// this.$post(path, {planId: this.tableData[i].id}, res => {
// let arr = res.data;
// let projectIds = arr.filter(d => d.id !== this.tableData[i].projectId).map(data => data.id);
// this.$set(this.tableData[i], "projectIds", projectIds);
// })
// }
this.unSelection = data.listObject.map(s => s.id);
});
},
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
handleSelect(selection, row) {
row.hashTree = [];
if (this.selectRows.has(row)) {
this.$set(row, "showMore", false);
this.selectRows.delete(row);
} else {
this.$set(row, "showMore", true);
this.selectRows.add(row);
}
let arr = Array.from(this.selectRows);
// 1
if (this.selectRows.size === 1) {
this.$set(arr[0], "showMore", true);
} else if (this.selectRows.size === 2) {
arr.forEach(row => {
this.$set(row, "showMore", true);
})
}
this.selectRowsCount(this.selectRows)
},
handleSelectAll(selection) {
if (selection.length > 0) {
if (selection.length === 1) {
selection.hashTree = [];
this.selectRows.add(selection[0]);
} else {
this.tableData.forEach(item => {
item.hashTree = [];
this.$set(item, "showMore", true);
this.selectRows.add(item);
});
}
} else {
this.selectRows.clear();
this.tableData.forEach(row => {
this.$set(row, "showMore", false);
})
}
this.selectRowsCount(this.selectRows)
},
handleDelete(testPlanReport) {
this.$alert(this.$t('report.delete_confirm') + ' ' + testPlanReport.name + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
@ -135,7 +204,32 @@ export default {
}
});
},
handleDeleteBatch(){
this.$alert(this.$t('report.delete_batch_confirm') + ' ' + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let deleteParam = {};
let ids = Array.from(this.selectRows).map(row => row.id);
deleteParam.dataIds = ids;
deleteParam.projectId = getCurrentProjectID();
deleteParam.selectAllDate = this.isSelectAllDate;
deleteParam.unSelectIds = this.unSelection;
deleteParam = Object.assign(deleteParam, this.condition);
this.$post('/test/plan/report/deleteBatchByParams/', deleteParam, () => {
this.$success(this.$t('commons.delete_success'));
this.initTableData();
this.selectRows.clear();
});
}
}
});
},
getIds(rowSets) {
let rowArray = Array.from(rowSets)
let ids = rowArray.map(s => s.id);
return ids;
},
filter(filters) {
_filter(filters, this.condition);
this.initTableData();
@ -149,6 +243,26 @@ export default {
this.$refs.testPlanReportView.open(planId);
}
},
isSelectDataAll(dataType) {
this.isSelectAllDate = dataType;
this.selectRowsCount(this.selectRows)
//
if (this.selectRows.size != this.tableData.length) {
this.$refs.testPlanReportTable.toggleAllSelection(true);
}
},
selectRowsCount(selection) {
let selectedIDs = this.getIds(selection);
let allIDs = this.tableData.map(s => s.id);
this.unSelection = allIDs.filter(function (val) {
return selectedIDs.indexOf(val) === -1
});
if (this.isSelectAllDate) {
this.selectDataCounts = this.total - this.unSelection.length;
} else {
this.selectDataCounts = selection.size;
}
},
}
}
</script>
@ -164,4 +278,22 @@ export default {
.el-table {
cursor: pointer;
}
.operate-button > div {
display: inline-block;
margin-left: 10px;
}
.request-method {
padding: 0 5px;
color: #1E90FF;
}
.ms-select-all >>> th:first-child {
margin-top: 20px;
}
.ms-select-all >>> th:nth-child(2) .el-icon-arrow-down {
top: -2px;
}
</style>

@ -1 +1 @@
Subproject commit 8a6a9ae708306eaf436f35394b71927cec075b0e
Subproject commit 5abe43dc1f65b529ad59c17bfdc58aea33d23cad

View File

@ -129,6 +129,7 @@ export default {
validate: "Validate",
batch_add: "Batch add",
check_project_tip: "Create or select the project first",
auth_redirect_tip: 'Jump to the authentication source page for authentication',
table: {
select_tip: "Item {0} data is selected"
},
@ -831,8 +832,8 @@ export default {
swagger_export_tip: "Export jSON-formatted files via Swagger website",
suffixFormatErr: "The file format does not meet the requirements",
swagger_url_import: "Import using URL",
timing_synchronization:"Timing synchronization",
next_synchronization_time:"Next synchronization time",
timing_synchronization: "Timing synchronization",
next_synchronization_time: "Next synchronization time",
},
home_page: {
@ -1493,6 +1494,6 @@ export default {
format: "Output format",
},
auth_source: {
delete_prompt:'This operation will delete the authentication source, do you want to continue? '
delete_prompt: 'This operation will delete the authentication source, do you want to continue? '
}
};

View File

@ -129,6 +129,7 @@ export default {
validate: "校验",
batch_add: "批量添加",
check_project_tip: "请先创建或选择项目",
auth_redirect_tip: '即将跳转到认证源页面进行认证',
table: {
select_tip: "已选中 {0} 条数据"
},
@ -833,8 +834,8 @@ export default {
swagger_export_tip: "通过 Swagger 页面导出",
suffixFormatErr: "文件格式不符合要求",
swagger_url_import: "使用URL导入",
timing_synchronization:"定时同步",
next_synchronization_time:"下次同步时间"
timing_synchronization: "定时同步",
next_synchronization_time: "下次同步时间"
},

View File

@ -129,6 +129,7 @@ export default {
validate: "校驗",
batch_add: "批量添加",
check_project_tip: "請先創建或選擇項目",
auth_redirect_tip: '即將跳轉到認證源頁面進行認證',
table: {
select_tip: "已选中 {0} 条数据"
},
@ -256,8 +257,8 @@ export default {
nail_robot: '釘釘機器人',
enterprise_wechat_robot: '企業微信機器人',
notes: '1.釘釘和企業群裏新建壹個自定義機器人,然後復制 webhook 地址在我們平臺上;\n' +
' 2.機器人選擇為群機器人,安全驗證選擇“自定義關鍵詞” "任務通知";\n' +
' 3.選擇接收人時必須是妳所建的群裏包含的人,接收人手機號為必填項且為釘釘企業所使用的手機號,',
' 2.機器人選擇為群機器人,安全驗證選擇“自定義關鍵詞” "任務通知";\n' +
' 3.選擇接收人時必須是妳所建的群裏包含的人,接收人手機號為必填項且為釘釘企業所使用的手機號,',
message: '事件,接收人,接收方式為必填項',
message_webhook: '接收方式為釘釘和企業機器人時webhook為必填項',
template: "模版"
@ -832,8 +833,8 @@ export default {
swagger_export_tip: "通過 Swagger 頁面導出",
suffixFormatErr: "文件格式不符合要求",
swagger_url_import: "使用URL導入",
timing_synchronization:"定時同步",
next_synchronization_time:"下次同步時間",
timing_synchronization: "定時同步",
next_synchronization_time: "下次同步時間",
},
home_page: {

View File

@ -15,7 +15,7 @@
</div>
<div class="form">
<el-form-item v-slot:default>
<el-radio-group v-model="form.authenticate">
<el-radio-group v-model="form.authenticate" @change="redirectAuth(form.authenticate)">
<el-radio label="LDAP" size="mini" v-if="openLdap">LDAP</el-radio>
<el-radio label="LOCAL" size="mini" v-if="openLdap">普通登录</el-radio>
<el-radio :label="auth.id" size="mini" v-for="auth in authSources" :key="auth.id">{{ auth.type }} {{ auth.name }}</el-radio>
@ -172,6 +172,11 @@ export default {
} else {
window.location.href = "/"
}
},
redirectAuth(authId) {
if (auth.default) {
auth.default.redirectAuth(this, authId);
}
}
}
}