Merge branch 'v1.1' of https://github.com/metersphere/metersphere into v1.1
This commit is contained in:
commit
0b1a0964d1
|
@ -172,7 +172,7 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
详细版本规划请参考 [版本路线图](https://github.com/metersphere/metersphere/blob/master/ROADMAP.md)
|
||||
详细的版本规划请参考 [版本路线图](https://github.com/metersphere/metersphere/blob/master/ROADMAP.md)
|
||||
|
||||
## 技术栈
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.45</version>
|
||||
<version>1.2.72</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -201,17 +201,6 @@
|
|||
<version>2.1.7</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
该依赖是私有仓库的依赖,现已经发布到 Github Packages,下载请在 settings 文件中配置自己的 GITHUB_TOKEN
|
||||
示例:
|
||||
<servers>
|
||||
<server>
|
||||
<id>github</id>
|
||||
<username>USERNAME</username>
|
||||
<password>TOKEN</password>
|
||||
</server>
|
||||
</servers>
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>com.fit2cloud</groupId>
|
||||
<artifactId>quartz-spring-boot-starter</artifactId>
|
||||
|
@ -358,18 +347,33 @@
|
|||
</plugins>
|
||||
</build>
|
||||
|
||||
<!--
|
||||
项目中依赖了某些私有仓库的包,现已经发布到 Github Packages,
|
||||
开发者下载依赖可按照 Github Maven 仓库的使用指导添加 Maven 仓库地址:https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-apache-maven-for-use-with-github-packages
|
||||
示例:下载请在 settings 文件中配置自己 Github 账号和 GITHUB_TOKEN
|
||||
<servers>
|
||||
<server>
|
||||
<id>fit2cloud</id>
|
||||
<username>USERNAME</username>
|
||||
<password>TOKEN</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>metersphere</id>
|
||||
<username>USERNAME</username>
|
||||
<password>TOKEN</password>
|
||||
</server>
|
||||
</servers>
|
||||
-->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>github</id>
|
||||
<id>fit2cloud</id>
|
||||
<name>fit2cloud</name>
|
||||
<url>https://maven.pkg.github.com/fit2cloud/quartz-spring-boot-starter</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>github</id>
|
||||
<id>metersphere</id>
|
||||
<name>metersphere</name>
|
||||
<url>https://maven.pkg.github.com/metersphere/jmeter-plugins-for-apache-dubbo</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
</project>
|
|
@ -3,6 +3,7 @@ package io.metersphere.api.controller;
|
|||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.api.dto.*;
|
||||
import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter;
|
||||
import io.metersphere.api.service.APITestService;
|
||||
import io.metersphere.base.domain.ApiTest;
|
||||
import io.metersphere.base.domain.Schedule;
|
||||
|
@ -98,4 +99,8 @@ public class APITestController {
|
|||
return apiTestService.apiTestImport(file, request);
|
||||
}
|
||||
|
||||
@PostMapping("/dubbo/providers")
|
||||
public List<DubboProvider> getProviders(@RequestBody RegistryCenter registry) {
|
||||
return apiTestService.getProviders(registry);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class DubboProvider {
|
||||
private String version;
|
||||
private String service;
|
||||
private String serviceInterface;
|
||||
private List<String> methods;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.api.dto.scenario;
|
||||
|
||||
import io.metersphere.api.dto.scenario.request.dubbo.ConfigCenter;
|
||||
import io.metersphere.api.dto.scenario.request.dubbo.ConsumerAndService;
|
||||
import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DubboConfig {
|
||||
private ConfigCenter configCenter;
|
||||
private RegistryCenter registryCenter;
|
||||
private ConsumerAndService consumerAndService;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package io.metersphere.api.dto.scenario;
|
||||
|
||||
import io.metersphere.api.dto.scenario.assertions.Assertions;
|
||||
import io.metersphere.api.dto.scenario.extract.Extract;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Request {
|
||||
private String name;
|
||||
private String url;
|
||||
private String method;
|
||||
private Boolean useEnvironment;
|
||||
private String path;
|
||||
private List<KeyValue> parameters;
|
||||
private List<KeyValue> headers;
|
||||
private Body body;
|
||||
private Assertions assertions;
|
||||
private Extract extract;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package io.metersphere.api.dto.scenario;
|
||||
|
||||
import io.metersphere.api.dto.scenario.request.Request;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -12,4 +13,5 @@ public class Scenario {
|
|||
private List<KeyValue> variables;
|
||||
private List<KeyValue> headers;
|
||||
private List<Request> requests;
|
||||
private DubboConfig dubboConfig;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package io.metersphere.api.dto.scenario.request;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.assertions.Assertions;
|
||||
import io.metersphere.api.dto.scenario.extract.Extract;
|
||||
import io.metersphere.api.dto.scenario.request.dubbo.ConfigCenter;
|
||||
import io.metersphere.api.dto.scenario.request.dubbo.ConsumerAndService;
|
||||
import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JSONType(typeName = RequestType.DUBBO)
|
||||
public class DubboRequest implements Request {
|
||||
// type 必须放最前面,以便能够转换正确的类
|
||||
private String type = RequestType.DUBBO;
|
||||
@JSONField(ordinal = 1)
|
||||
private String name;
|
||||
@JSONField(ordinal = 2)
|
||||
private String protocol;
|
||||
@JsonProperty(value = "interface")
|
||||
@JSONField(ordinal = 3, name = "interface")
|
||||
private String _interface;
|
||||
@JSONField(ordinal = 4)
|
||||
private String method;
|
||||
|
||||
@JSONField(ordinal = 5)
|
||||
private ConfigCenter configCenter;
|
||||
@JSONField(ordinal = 6)
|
||||
private RegistryCenter registryCenter;
|
||||
@JSONField(ordinal = 7)
|
||||
private ConsumerAndService consumerAndService;
|
||||
|
||||
@JSONField(ordinal = 8)
|
||||
private List<KeyValue> args;
|
||||
@JSONField(ordinal = 9)
|
||||
private List<KeyValue> attachmentArgs;
|
||||
@JSONField(ordinal = 10)
|
||||
private Assertions assertions;
|
||||
@JSONField(ordinal = 11)
|
||||
private Extract extract;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.metersphere.api.dto.scenario.request;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.assertions.Assertions;
|
||||
import io.metersphere.api.dto.scenario.extract.Extract;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JSONType(typeName = RequestType.HTTP)
|
||||
public class HttpRequest implements Request {
|
||||
// type 必须放最前面,以便能够转换正确的类
|
||||
private String type = RequestType.HTTP;
|
||||
@JSONField(ordinal = 1)
|
||||
private String name;
|
||||
@JSONField(ordinal = 2)
|
||||
private String url;
|
||||
@JSONField(ordinal = 3)
|
||||
private String method;
|
||||
@JSONField(ordinal = 4)
|
||||
private String path;
|
||||
@JSONField(ordinal = 5)
|
||||
private Boolean useEnvironment;
|
||||
@JSONField(ordinal = 6)
|
||||
private List<KeyValue> parameters;
|
||||
@JSONField(ordinal = 7)
|
||||
private List<KeyValue> headers;
|
||||
@JSONField(ordinal = 8)
|
||||
private Body body;
|
||||
@JSONField(ordinal = 9)
|
||||
private Assertions assertions;
|
||||
@JSONField(ordinal = 10)
|
||||
private Extract extract;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.metersphere.api.dto.scenario.request;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = HttpRequest.class, name = RequestType.HTTP),
|
||||
@JsonSubTypes.Type(value = DubboRequest.class, name = RequestType.DUBBO)
|
||||
})
|
||||
@JSONType(seeAlso = {HttpRequest.class, DubboRequest.class}, typeKey = "type")
|
||||
public interface Request {
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package io.metersphere.api.dto.scenario.request;
|
||||
|
||||
public class RequestType {
|
||||
|
||||
public static final String HTTP = "HTTP";
|
||||
|
||||
public static final String DUBBO = "DUBBO";
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.metersphere.api.dto.scenario.request.dubbo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ConfigCenter {
|
||||
private String protocol;
|
||||
private String group;
|
||||
private String namespace;
|
||||
private String username;
|
||||
private String address;
|
||||
private String password;
|
||||
private String timeout;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package io.metersphere.api.dto.scenario.request.dubbo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ConsumerAndService {
|
||||
private String timeout;
|
||||
private String version;
|
||||
private String retries;
|
||||
private String cluster;
|
||||
private String group;
|
||||
private String connections;
|
||||
private String async;
|
||||
private String loadBalance;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.api.dto.scenario.request.dubbo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RegistryCenter {
|
||||
private String protocol;
|
||||
private String group;
|
||||
private String username;
|
||||
private String address;
|
||||
private String password;
|
||||
private String timeout;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import io.github.ningyu.jmeter.plugin.dubbo.sample.ProviderService;
|
||||
import io.metersphere.api.service.APIReportService;
|
||||
import io.metersphere.api.service.APITestService;
|
||||
import io.metersphere.commons.constants.APITestStatus;
|
||||
|
@ -107,14 +108,11 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
}
|
||||
|
||||
private RequestResult getRequestResult(SampleResult result) {
|
||||
String body = result.getSamplerData();
|
||||
String method = StringUtils.substringBefore(body, " ");
|
||||
|
||||
RequestResult requestResult = new RequestResult();
|
||||
requestResult.setName(result.getSampleLabel());
|
||||
requestResult.setUrl(result.getUrlAsString());
|
||||
requestResult.setMethod(method);
|
||||
requestResult.setBody(body);
|
||||
requestResult.setMethod(getMethod(result));
|
||||
requestResult.setBody(result.getSamplerData());
|
||||
requestResult.setHeaders(result.getRequestHeaders());
|
||||
requestResult.setRequestSize(result.getSentBytes());
|
||||
requestResult.setTotalAssertions(result.getAssertionResults().length);
|
||||
|
@ -143,6 +141,19 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
return requestResult;
|
||||
}
|
||||
|
||||
private String getMethod(SampleResult result) {
|
||||
String body = result.getSamplerData();
|
||||
// Dubbo Protocol
|
||||
String start = "RPC Protocol: ";
|
||||
String end = "://";
|
||||
if (StringUtils.contains(body, start)) {
|
||||
return StringUtils.substringBetween(body, start, end).toUpperCase();
|
||||
} else {
|
||||
// Http Method
|
||||
return StringUtils.substringBefore(body, " ");
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) {
|
||||
ResponseAssertionResult responseAssertionResult = new ResponseAssertionResult();
|
||||
responseAssertionResult.setMessage(assertionResult.getFailureMessage());
|
||||
|
|
|
@ -2,8 +2,8 @@ package io.metersphere.api.parse;
|
|||
|
||||
import io.metersphere.api.dto.ApiTestImportRequest;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.Request;
|
||||
import io.metersphere.api.dto.scenario.Scenario;
|
||||
import io.metersphere.api.dto.scenario.request.HttpRequest;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -50,11 +50,11 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
|
|||
}
|
||||
}
|
||||
|
||||
protected void addContentType(Request request, String contentType) {
|
||||
protected void addContentType(HttpRequest request, String contentType) {
|
||||
addHeader(request, HttpHeader.CONTENT_TYPE.toString(), contentType);
|
||||
}
|
||||
|
||||
protected void addCookie(Request request, String key, String value) {
|
||||
protected void addCookie(HttpRequest request, String key, String value) {
|
||||
List<KeyValue> headers = Optional.ofNullable(request.getHeaders()).orElse(new ArrayList<>());
|
||||
boolean hasCookie = false;
|
||||
for (KeyValue header : headers) {
|
||||
|
@ -69,7 +69,7 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
|
|||
}
|
||||
}
|
||||
|
||||
protected void addHeader(Request request, String key, String value) {
|
||||
protected void addHeader(HttpRequest request, String key, String value) {
|
||||
List<KeyValue> headers = Optional.ofNullable(request.getHeaders()).orElse(new ArrayList<>());
|
||||
boolean hasContentType = false;
|
||||
for (KeyValue header : headers) {
|
||||
|
|
|
@ -7,8 +7,9 @@ import io.metersphere.api.dto.parse.ApiImport;
|
|||
import io.metersphere.api.dto.parse.postman.*;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.Request;
|
||||
import io.metersphere.api.dto.scenario.request.HttpRequest;
|
||||
import io.metersphere.api.dto.scenario.Scenario;
|
||||
import io.metersphere.api.dto.scenario.request.Request;
|
||||
import io.metersphere.commons.constants.MsRequestBodyType;
|
||||
import io.metersphere.commons.constants.PostmanRequestBodyMode;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -62,7 +63,7 @@ public class PostmanParser extends ApiImportAbstractParser {
|
|||
List<PostmanItem> item = postmanCollection.getItem();
|
||||
List<Request> requests = new ArrayList<>();
|
||||
for (PostmanItem requestItem : item) {
|
||||
Request request = new Request();
|
||||
HttpRequest request = new HttpRequest();
|
||||
PostmanRequest requestDesc = requestItem.getRequest();
|
||||
PostmanUrl url = requestDesc.getUrl();
|
||||
request.setName(requestItem.getName());
|
||||
|
@ -77,7 +78,7 @@ public class PostmanParser extends ApiImportAbstractParser {
|
|||
return requests;
|
||||
}
|
||||
|
||||
private Body parseBody(PostmanRequest requestDesc, Request request) {
|
||||
private Body parseBody(PostmanRequest requestDesc, HttpRequest request) {
|
||||
Body body = new Body();
|
||||
JSONObject postmanBody = requestDesc.getBody();
|
||||
if (postmanBody == null) {
|
||||
|
|
|
@ -6,8 +6,9 @@ import io.metersphere.api.dto.ApiTestImportRequest;
|
|||
import io.metersphere.api.dto.parse.ApiImport;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.Request;
|
||||
import io.metersphere.api.dto.scenario.request.HttpRequest;
|
||||
import io.metersphere.api.dto.scenario.Scenario;
|
||||
import io.metersphere.api.dto.scenario.request.Request;
|
||||
import io.metersphere.commons.constants.MsRequestBodyType;
|
||||
import io.metersphere.commons.constants.SwaggerParameterType;
|
||||
import io.swagger.models.*;
|
||||
|
@ -51,7 +52,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
Set<HttpMethod> httpMethods = operationMap.keySet();
|
||||
for (HttpMethod method : httpMethods) {
|
||||
Operation operation = operationMap.get(method);
|
||||
Request request = new Request();
|
||||
HttpRequest request = new HttpRequest();
|
||||
request.setName(operation.getOperationId());
|
||||
request.setPath(pathName);
|
||||
request.setUseEnvironment(true);
|
||||
|
@ -76,13 +77,11 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
|
||||
}
|
||||
}
|
||||
scenarioMap.values().forEach(scenario -> {
|
||||
scenarios.add(scenario);
|
||||
});
|
||||
scenarios.addAll(scenarioMap.values());
|
||||
return scenarios;
|
||||
}
|
||||
|
||||
private void parseParameters(Operation operation, Map<String, Model> definitions, Request request) {
|
||||
private void parseParameters(Operation operation, Map<String, Model> definitions, HttpRequest request) {
|
||||
|
||||
List<Parameter> parameters = operation.getParameters();
|
||||
|
||||
|
@ -113,17 +112,17 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
}
|
||||
}
|
||||
|
||||
private void parseCookieParameters(Parameter parameter, Request request) {
|
||||
private void parseCookieParameters(Parameter parameter, HttpRequest request) {
|
||||
CookieParameter cookieParameter = (CookieParameter) parameter;
|
||||
addCookie(request, cookieParameter.getName(), cookieParameter.getDescription());
|
||||
}
|
||||
|
||||
private void parseHeaderParameters(Parameter parameter, Request request) {
|
||||
private void parseHeaderParameters(Parameter parameter, HttpRequest request) {
|
||||
HeaderParameter headerParameter = (HeaderParameter) parameter;
|
||||
addHeader(request, headerParameter.getName(), headerParameter.getDescription());
|
||||
}
|
||||
|
||||
private void parseBodyParameters(Parameter parameter, Request request, Map<String, Model> definitions) {
|
||||
private void parseBodyParameters(Parameter parameter, HttpRequest request, Map<String, Model> definitions) {
|
||||
BodyParameter bodyParameter = (BodyParameter) parameter;
|
||||
Body body = Optional.ofNullable(request.getBody()).orElse(new Body());
|
||||
body.setType(MsRequestBodyType.RAW.value());
|
||||
|
@ -175,7 +174,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
return jsonObject;
|
||||
}
|
||||
|
||||
private void parseFormDataParameters(Parameter parameter, Request request) {
|
||||
private void parseFormDataParameters(Parameter parameter, HttpRequest request) {
|
||||
Body body = Optional.ofNullable(request.getBody()).orElse(new Body());
|
||||
body.setType(MsRequestBodyType.FORM_DATA.value());
|
||||
List<KeyValue> keyValues = Optional.ofNullable(body.getKvs()).orElse(new ArrayList<>());
|
||||
|
@ -184,7 +183,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
request.setBody(body);
|
||||
}
|
||||
|
||||
private void parseQueryParameters(Parameter parameter, Request request) {
|
||||
private void parseQueryParameters(Parameter parameter, HttpRequest request) {
|
||||
QueryParameter queryParameter = (QueryParameter) parameter;
|
||||
List<KeyValue> parameters = Optional.ofNullable(request.getParameters()).orElse(new ArrayList<>());
|
||||
parameters.add(new KeyValue(queryParameter.getName(), "", queryParameter.getDescription()));
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.api.dto.APITestResult;
|
||||
import io.metersphere.api.dto.ApiTestImportRequest;
|
||||
import io.metersphere.api.dto.QueryAPITestRequest;
|
||||
import io.metersphere.api.dto.SaveAPITestRequest;
|
||||
import io.github.ningyu.jmeter.plugin.dubbo.sample.ProviderService;
|
||||
import io.metersphere.api.dto.*;
|
||||
import io.metersphere.api.dto.parse.ApiImport;
|
||||
import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter;
|
||||
import io.metersphere.api.jmeter.JMeterService;
|
||||
import io.metersphere.api.parse.ApiImportParser;
|
||||
import io.metersphere.api.parse.ApiImportParserFactory;
|
||||
|
@ -27,18 +26,17 @@ import io.metersphere.job.sechedule.ApiTestJob;
|
|||
import io.metersphere.service.FileService;
|
||||
import io.metersphere.service.ScheduleService;
|
||||
import io.metersphere.track.service.TestCaseService;
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
|
@ -290,4 +288,23 @@ public class APITestService {
|
|||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
public List<DubboProvider> getProviders(RegistryCenter registry) {
|
||||
ProviderService providerService = ProviderService.get("provider");
|
||||
List<String> providers = providerService.getProviders(registry.getProtocol(), registry.getAddress(), registry.getGroup());
|
||||
List<DubboProvider> providerList = new ArrayList<>();
|
||||
providers.forEach(p -> {
|
||||
Map<String, URL> services = providerService.findByService(p);
|
||||
services.forEach((k, v) -> {
|
||||
DubboProvider provider = new DubboProvider();
|
||||
provider.setVersion(v.getParameter("version"));
|
||||
provider.setService(v.getServiceKey());
|
||||
provider.setServiceInterface(v.getServiceInterface());
|
||||
String[] methods = v.getParameter("methods").split(",");
|
||||
provider.setMethods(Arrays.asList(methods));
|
||||
providerList.add(provider);
|
||||
});
|
||||
});
|
||||
return providerList;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
<select id="getLoadTestByProjectId" resultType="io.metersphere.base.domain.LoadTest">
|
||||
select id,name
|
||||
from load_test
|
||||
where project_id = #{projectId};
|
||||
where project_id = #{projectId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -1,5 +1,6 @@
|
|||
package io.metersphere.track.domain;
|
||||
|
||||
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
|
||||
import io.metersphere.track.dto.TestCaseReportMetricDTO;
|
||||
import io.metersphere.track.dto.TestCaseReportStatusResultDTO;
|
||||
import io.metersphere.track.dto.TestPlanCaseDTO;
|
||||
|
@ -7,6 +8,7 @@ import io.metersphere.track.dto.TestPlanDTO;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReportResultChartComponent extends ReportComponent {
|
||||
|
@ -25,7 +27,24 @@ public class ReportResultChartComponent extends ReportComponent {
|
|||
|
||||
@Override
|
||||
public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) {
|
||||
testCaseReportMetric.setExecuteResult(new ArrayList<>(reportStatusResultMap.values()));
|
||||
testCaseReportMetric.setExecuteResult(getReportStatusResult());
|
||||
}
|
||||
|
||||
private List<TestCaseReportStatusResultDTO> getReportStatusResult() {
|
||||
List<TestCaseReportStatusResultDTO> reportStatusResult = new ArrayList<>();
|
||||
addToReportStatusResultList(reportStatusResult, TestPlanTestCaseStatus.Pass.name());
|
||||
addToReportStatusResultList(reportStatusResult, TestPlanTestCaseStatus.Failure.name());
|
||||
addToReportStatusResultList(reportStatusResult, TestPlanTestCaseStatus.Blocking.name());
|
||||
addToReportStatusResultList(reportStatusResult, TestPlanTestCaseStatus.Skip.name());
|
||||
addToReportStatusResultList(reportStatusResult, TestPlanTestCaseStatus.Underway.name());
|
||||
addToReportStatusResultList(reportStatusResult, TestPlanTestCaseStatus.Prepare.name());
|
||||
return reportStatusResult;
|
||||
}
|
||||
|
||||
private void addToReportStatusResultList(List<TestCaseReportStatusResultDTO> reportStatusResultList, String status) {
|
||||
if (reportStatusResultMap.get(status) != null) {
|
||||
reportStatusResultList.add(reportStatusResultMap.get(status));
|
||||
}
|
||||
}
|
||||
|
||||
private void getStatusResultMap(Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap, TestPlanCaseDTO testCase) {
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
import {Test} from "./model/ScenarioModel"
|
||||
import MsApiReportStatus from "../report/ApiReportStatus";
|
||||
import MsApiReportDialog from "./ApiReportDialog";
|
||||
import {checkoutTestManagerOrTestUser, downloadFile} from "../../../../common/js/utils";
|
||||
import {checkoutTestManagerOrTestUser, downloadFile} from "@/common/js/utils";
|
||||
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
||||
import ApiImport from "./components/import/ApiImport";
|
||||
|
||||
|
|
|
@ -6,15 +6,17 @@
|
|||
<div class="kv-row" v-for="(item, index) in items" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
<el-col>
|
||||
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="100" @change="change"
|
||||
:placeholder="$t('api_test.key')" show-word-limit/>
|
||||
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="100"
|
||||
@change="change"
|
||||
:placeholder="keyText" show-word-limit/>
|
||||
<el-autocomplete :maxlength="100" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="$t('api_test.key')" show-word-limit/>
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText"
|
||||
show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-input :disabled="isReadOnly" v-model="item.value" size="small" maxlength="500" @change="change"
|
||||
:placeholder="$t('api_test.value')" show-word-limit/>
|
||||
:placeholder="valueText" show-word-limit/>
|
||||
</el-col>
|
||||
<el-col class="kv-delete">
|
||||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||
|
@ -32,6 +34,8 @@
|
|||
name: "MsApiKeyValue",
|
||||
|
||||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
description: String,
|
||||
items: Array,
|
||||
isReadOnly: {
|
||||
|
@ -41,6 +45,15 @@
|
|||
suggestions: Array
|
||||
},
|
||||
|
||||
computed: {
|
||||
keyText() {
|
||||
return this.keyPlaceholder || this.$t("api_test.key");
|
||||
},
|
||||
valueText() {
|
||||
return this.valuePlaceholder || this.$t("api_test.value");
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
remove: function (index) {
|
||||
this.items.splice(index, 1);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<ms-api-request-config :is-read-only="isReadOnly" :scenario="scenario" :open="select"/>
|
||||
<ms-api-request-config :is-read-only="isReadOnly" :scenario="scenario" @select="select"/>
|
||||
</ms-api-collapse-item>
|
||||
</draggable>
|
||||
</ms-api-collapse>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<el-main class="scenario-main">
|
||||
<div class="scenario-form">
|
||||
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" :project-id="projectId" v-if="isScenario"/>
|
||||
<ms-api-request-form :is-read-only="isReadOnly" :request="selected" :project-id="projectId" v-if="isRequest"/>
|
||||
<ms-api-request-form :is-read-only="isReadOnly" :request="selected" v-if="isRequest"/>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
@ -46,8 +46,8 @@
|
|||
|
||||
import MsApiCollapseItem from "./collapse/ApiCollapseItem";
|
||||
import MsApiCollapse from "./collapse/ApiCollapse";
|
||||
import MsApiRequestConfig from "./ApiRequestConfig";
|
||||
import MsApiRequestForm from "./ApiRequestForm";
|
||||
import MsApiRequestConfig from "./request/ApiRequestConfig";
|
||||
import MsApiRequestForm from "./request/ApiRequestForm";
|
||||
import MsApiScenarioForm from "./ApiScenarioForm";
|
||||
import {Scenario, Request} from "../model/ScenarioModel";
|
||||
import draggable from 'vuedraggable';
|
||||
|
|
|
@ -5,12 +5,19 @@
|
|||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('api_test.environment.environment')">
|
||||
<el-select :disabled="isReadOnly" v-model="scenario.environmentId" class="environment-select" @change="environmentChange" clearable>
|
||||
<el-option v-for="(environment, index) in environments" :key="index" :label="environment.name + ': ' + environment.protocol + '://' + environment.socket" :value="environment.id"/>
|
||||
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">{{$t('api_test.environment.environment_config')}}</el-button>
|
||||
<el-select :disabled="isReadOnly" v-model="scenario.environmentId" class="environment-select"
|
||||
@change="environmentChange" clearable>
|
||||
<el-option v-for="(environment, index) in environments" :key="index"
|
||||
:label="environment.name + ': ' + environment.protocol + '://' + environment.socket"
|
||||
:value="environment.id"/>
|
||||
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
|
||||
{{$t('api_test.environment.environment_config')}}
|
||||
</el-button>
|
||||
<template v-slot:empty>
|
||||
<div class="empty-environment">
|
||||
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">{{$t('api_test.environment.environment_config')}}</el-button>
|
||||
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
|
||||
{{$t('api_test.environment.environment_config')}}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-select>
|
||||
|
@ -18,10 +25,20 @@
|
|||
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane :label="$t('api_test.scenario.variables')" name="parameters">
|
||||
<ms-api-scenario-variables :is-read-only="isReadOnly" :items="scenario.variables" :description="$t('api_test.scenario.kv_description')"/>
|
||||
<ms-api-scenario-variables :is-read-only="isReadOnly" :items="scenario.variables"
|
||||
:description="$t('api_test.scenario.kv_description')"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.scenario.headers')" name="headers">
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :items="scenario.headers" :suggestions="headerSuggestions" :description="$t('api_test.scenario.kv_description')"/>
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :items="scenario.headers" :suggestions="headerSuggestions"
|
||||
:description="$t('api_test.scenario.kv_description')"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.scenario.dubbo')" name="dubbo">
|
||||
<div class="dubbo-config-title">Config Center</div>
|
||||
<ms-dubbo-config-center :config="scenario.dubboConfig.configCenter"/>
|
||||
<div class="dubbo-config-title">Registry Center</div>
|
||||
<ms-dubbo-registry-center :registry="scenario.dubboConfig.registryCenter"/>
|
||||
<div class="dubbo-config-title">Consumer & Service</div>
|
||||
<ms-dubbo-consumer-service :consumer="scenario.dubboConfig.consumerAndService"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
|
@ -36,11 +53,17 @@
|
|||
import {Scenario} from "../model/ScenarioModel";
|
||||
import MsApiScenarioVariables from "./ApiScenarioVariables";
|
||||
import ApiEnvironmentConfig from "./ApiEnvironmentConfig";
|
||||
import {requestHeaders} from "../../../../../common/js/constants";
|
||||
import {REQUEST_HEADERS} from "@/common/js/constants";
|
||||
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";
|
||||
|
||||
export default {
|
||||
name: "MsApiScenarioForm",
|
||||
components: {ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue},
|
||||
components: {
|
||||
MsDubboConsumerService,
|
||||
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue
|
||||
},
|
||||
props: {
|
||||
scenario: Scenario,
|
||||
projectId: String,
|
||||
|
@ -65,7 +88,7 @@
|
|||
{max: 100, message: this.$t('commons.input_limit', [1, 100]), trigger: 'blur'}
|
||||
]
|
||||
},
|
||||
headerSuggestions: requestHeaders
|
||||
headerSuggestions: REQUEST_HEADERS
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -136,7 +159,13 @@
|
|||
}
|
||||
|
||||
.empty-environment {
|
||||
padding: 10px 0px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.dubbo-config-title {
|
||||
margin-bottom: 10px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -24,5 +24,7 @@
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.request-method-select {
|
||||
width: 110px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
import MsApiScenarioVariables from "../ApiScenarioVariables";
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import {requestHeaders} from "../../../../../../common/js/constants";
|
||||
import {REQUEST_HEADERS} from "../../../../../../common/js/constants";
|
||||
|
||||
export default {
|
||||
name: "EnvironmentEdit",
|
||||
|
@ -71,7 +71,7 @@
|
|||
],
|
||||
socket :[{required: true, validator: socketValidator, trigger: 'blur'}],
|
||||
},
|
||||
headerSuggestions: requestHeaders
|
||||
headerSuggestions: REQUEST_HEADERS
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<el-form :model="request" :rules="rules" ref="request" label-width="100px" :disabled="isReadOnly">
|
||||
|
||||
<el-form-item :label="$t('api_test.request.name')" prop="name">
|
||||
<el-input v-model="request.name" maxlength="300" show-word-limit/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('api_test.request.dubbo.protocol')" prop="protocol">
|
||||
<el-select v-model="request.protocol">
|
||||
<el-option label="dubbo://" :value="protocols.DUBBO"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="Interface" name="interface">
|
||||
<ms-dubbo-interface :request="request" :is-read-only="isReadOnly"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Config Center" name="config">
|
||||
<ms-dubbo-config-center :config="request.configCenter" :is-read-only="isReadOnly"
|
||||
:description="$t('api_test.request.dubbo.form_description')"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Registry Center" name="registry">
|
||||
<ms-dubbo-registry-center :registry="request.registryCenter" :is-read-only="isReadOnly"
|
||||
:description="$t('api_test.request.dubbo.form_description')"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Consumer & Service" name="consumer">
|
||||
<ms-dubbo-consumer-service :consumer="request.consumerAndService" :is-read-only="isReadOnly"
|
||||
:description="$t('api_test.request.dubbo.form_description')"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-tabs v-model="activeName2">
|
||||
<el-tab-pane label="Args" name="args">
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :items="request.args"
|
||||
key-placeholder="Param Type" value-placeholder="Param Value"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Attachment Args" name="attachment">
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :items="request.attachmentArgs"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions">
|
||||
<ms-api-assertions :is-read-only="isReadOnly" :assertions="request.assertions"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.extract.label')" name="extract">
|
||||
<ms-api-extract :is-read-only="isReadOnly" :extract="request.extract"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import MsApiBody from "../ApiBody";
|
||||
import MsApiAssertions from "../assertion/ApiAssertions";
|
||||
import {DubboRequest} from "../../model/ScenarioModel";
|
||||
import MsApiExtract from "../extract/ApiExtract";
|
||||
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
|
||||
import MsDubboInterface from "@/business/components/api/test/components/request/dubbo/Interface";
|
||||
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";
|
||||
|
||||
export default {
|
||||
name: "MsApiDubboRequestForm",
|
||||
components: {
|
||||
MsDubboConsumerService,
|
||||
MsDubboConfigCenter,
|
||||
MsDubboRegistryCenter,
|
||||
MsDubboInterface, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue
|
||||
},
|
||||
props: {
|
||||
request: DubboRequest,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeName: "interface",
|
||||
activeName2: "args",
|
||||
protocols: DubboRequest.PROTOCOLS,
|
||||
rules: {
|
||||
name: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
useEnvironmentChange(value) {
|
||||
if (value && !this.request.environment) {
|
||||
this.$error(this.$t('api_test.request.please_add_environment_to_scenario'), 2000);
|
||||
this.request.useEnvironment = false;
|
||||
}
|
||||
this.$refs["request"].clearValidate();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-method-select {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.environment-display {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.environment-name {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.adjust-margin-bottom {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.environment-url-tip {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -5,7 +5,8 @@
|
|||
<el-input :disabled="isReadOnly" v-model="request.name" maxlength="300" show-word-limit/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!request.useEnvironment" :label="$t('api_test.request.url')" prop="url" class="adjust-margin-bottom">
|
||||
<el-form-item v-if="!request.useEnvironment" :label="$t('api_test.request.url')" prop="url"
|
||||
class="adjust-margin-bottom">
|
||||
<el-input :disabled="isReadOnly" v-model="request.url" maxlength="500"
|
||||
:placeholder="$t('api_test.request.url_description')" @change="urlChange" clearable>
|
||||
<template v-slot:prepend>
|
||||
|
@ -60,19 +61,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValue from "./ApiKeyValue";
|
||||
import MsApiBody from "./ApiBody";
|
||||
import MsApiAssertions from "./assertion/ApiAssertions";
|
||||
import {KeyValue, Request} from "../model/ScenarioModel";
|
||||
import MsApiExtract from "./extract/ApiExtract";
|
||||
import ApiRequestMethodSelect from "./collapse/ApiRequestMethodSelect";
|
||||
import {requestHeaders} from "../../../../../common/js/constants";
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import MsApiBody from "../ApiBody";
|
||||
import MsApiAssertions from "../assertion/ApiAssertions";
|
||||
import {KeyValue} from "../../model/ScenarioModel";
|
||||
import MsApiExtract from "../extract/ApiExtract";
|
||||
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
|
||||
import {REQUEST_HEADERS} from "@/common/js/constants";
|
||||
import {HttpRequest} from "../../model/ScenarioModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiRequestForm",
|
||||
name: "MsApiHttpRequestForm",
|
||||
components: {ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
|
||||
props: {
|
||||
request: Request,
|
||||
request: HttpRequest,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -101,7 +103,7 @@
|
|||
{max: 500, required: true, message: this.$t('commons.input_limit', [1, 500]), trigger: 'blur'},
|
||||
]
|
||||
},
|
||||
headerSuggestions: requestHeaders
|
||||
headerSuggestions: REQUEST_HEADERS
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -119,7 +121,8 @@
|
|||
this.request.path = '/' + this.request.path;
|
||||
}
|
||||
let url = this.getURL(this.displayUrl);
|
||||
this.request.path = decodeURIComponent(url.pathname);
|
||||
this
|
||||
.request.path = decodeURIComponent(url.pathname);
|
||||
this.request.urlWirhEnv = decodeURIComponent(url.origin + url.pathname);
|
||||
},
|
||||
getURL(urlStr) {
|
||||
|
@ -169,10 +172,6 @@
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-method-select {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
width: 100%;
|
||||
height: 40px;
|
|
@ -3,9 +3,12 @@
|
|||
<draggable :list="this.scenario.requests" group="Request" class="request-draggable" ghost-class="request-ghost">
|
||||
<div class="request-item" v-for="(request, index) in this.scenario.requests" :key="index" @click="select(request)"
|
||||
:class="{'selected': isSelected(request)}">
|
||||
<el-row type="flex">
|
||||
<el-row type="flex" align="middle">
|
||||
<div class="request-type">
|
||||
{{request.showType()}}
|
||||
</div>
|
||||
<div class="request-method">
|
||||
{{request.method}}
|
||||
{{request.showMethod()}}
|
||||
</div>
|
||||
<div class="request-name">
|
||||
{{request.name}}
|
||||
|
@ -26,12 +29,20 @@
|
|||
</el-row>
|
||||
</div>
|
||||
</draggable>
|
||||
<el-button :disabled="isReadOnly" class="request-create" type="primary" size="mini" icon="el-icon-plus" plain @click="createRequest"/>
|
||||
<el-popover placement="top" v-model="visible">
|
||||
<el-radio-group v-model="type" @change="createRequest">
|
||||
<el-radio :label="types.HTTP">HTTP</el-radio>
|
||||
<el-radio :label="types.DUBBO">DUBBO</el-radio>
|
||||
</el-radio-group>
|
||||
<el-button slot="reference" :disabled="isReadOnly"
|
||||
class="request-create" type="primary" size="mini" icon="el-icon-plus" plain/>
|
||||
</el-popover>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Request} from "../model/ScenarioModel";
|
||||
import {RequestFactory} from "../../model/ScenarioModel";
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
|
@ -41,7 +52,6 @@
|
|||
|
||||
props: {
|
||||
scenario: Object,
|
||||
open: Function,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -51,6 +61,9 @@
|
|||
data() {
|
||||
return {
|
||||
selected: 0,
|
||||
visible: false,
|
||||
types: RequestFactory.TYPES,
|
||||
type: ""
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -63,13 +76,15 @@
|
|||
},
|
||||
|
||||
methods: {
|
||||
createRequest: function () {
|
||||
let request = new Request();
|
||||
createRequest: function (type) {
|
||||
let request = new RequestFactory({type: type});
|
||||
this.scenario.requests.push(request);
|
||||
this.type = "";
|
||||
this.visible = false;
|
||||
},
|
||||
copyRequest: function (index) {
|
||||
let request = this.scenario.requests[index];
|
||||
this.scenario.requests.push(new Request(request));
|
||||
this.scenario.requests.push(new RequestFactory(request));
|
||||
},
|
||||
deleteRequest: function (index) {
|
||||
this.scenario.requests.splice(index, 1);
|
||||
|
@ -92,8 +107,9 @@
|
|||
if (!request.useEnvironment) {
|
||||
request.useEnvironment = false;
|
||||
}
|
||||
request.dubboConfig = this.scenario.dubboConfig;
|
||||
this.selected = request;
|
||||
this.open(request);
|
||||
this.$emit("select", request);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -106,7 +122,6 @@
|
|||
<style scoped>
|
||||
.request-item {
|
||||
border-left: 5px solid #1E90FF;
|
||||
line-height: 40px;
|
||||
max-height: 40px;
|
||||
border-top: 1px solid #EBEEF5;
|
||||
cursor: pointer;
|
||||
|
@ -124,6 +139,19 @@
|
|||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.request-type {
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
margin-left: 5px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.request-method {
|
||||
padding: 0 5px;
|
||||
color: #1E90FF;
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<component :is="component" :is-read-only="isReadOnly" :request="request"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Request, RequestFactory} from "../../model/ScenarioModel";
|
||||
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
|
||||
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
|
||||
|
||||
export default {
|
||||
name: "MsApiRequestForm",
|
||||
components: {MsApiDubboRequestForm, MsApiHttpRequestForm},
|
||||
props: {
|
||||
request: Request,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
component({request: {type}}) {
|
||||
let name;
|
||||
switch (type) {
|
||||
case RequestFactory.TYPES.DUBBO:
|
||||
name = "MsApiDubboRequestForm";
|
||||
break;
|
||||
default:
|
||||
name = "MsApiHttpRequestForm";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<el-form :model="config" :rules="rules" ref="config" label-width="100px" size="small" :disabled="isReadOnly">
|
||||
<div class="dubbo-form-description" v-if="description">
|
||||
{{description}}
|
||||
</div>
|
||||
<el-form-item label="Protocol" prop="protocol" class="dubbo-form-item">
|
||||
<el-select v-model="config.protocol" class="select-100">
|
||||
<el-option v-for="p in protocols" :key="p" :label="p" :value="p"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Group" prop="group" class="dubbo-form-item">
|
||||
<el-input v-model="config.group" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Namespace" prop="namespace" class="dubbo-form-item">
|
||||
<el-input v-model="config.namespace" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Timeout" prop="timeout" class="dubbo-form-item">
|
||||
<el-input type="number" v-model="config.timeout" :placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Address" prop="address" class="dubbo-form-item-long">
|
||||
<el-input v-model="config.address" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="UserName" prop="username" class="dubbo-form-item">
|
||||
<el-input v-model="config.username" maxlength="100" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Password" prop="password" class="dubbo-form-item">
|
||||
<el-input v-model="config.password" maxlength="30" show-word-limit show-password autocomplete="new-password"
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './dubbo.css'
|
||||
import {ConfigCenter} from "@/business/components/api/test/model/ScenarioModel";
|
||||
|
||||
export default {
|
||||
name: "MsDubboConfigCenter",
|
||||
props: {
|
||||
description: String,
|
||||
config: ConfigCenter,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
protocols: ConfigCenter.PROTOCOLS,
|
||||
methods: [],
|
||||
rules: {
|
||||
group: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
],
|
||||
namespace: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
],
|
||||
username: [
|
||||
{max: 100, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
],
|
||||
password: [
|
||||
{max: 30, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
],
|
||||
address: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
|
||||
<el-form :model="consumer" :rules="rules" ref="consumer" label-width="100px" size="small" :disabled="isReadOnly">
|
||||
<div class="dubbo-form-description" v-if="description">
|
||||
{{description}}
|
||||
</div>
|
||||
<el-form-item label="Timeout" prop="timeout" class="dubbo-form-item">
|
||||
<el-input type="number" v-model="consumer.timeout" :placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Version" prop="version" class="dubbo-form-item">
|
||||
<el-input v-model="consumer.version" maxlength="30" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Retries" prop="retries" class="dubbo-form-item">
|
||||
<el-input type="number" v-model="consumer.retries" :placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Cluster" prop="cluster" class="dubbo-form-item">
|
||||
<el-input v-model="consumer.cluster" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Group" prop="group" class="dubbo-form-item">
|
||||
<el-input v-model="consumer.group" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Connections" prop="connections" class="dubbo-form-item">
|
||||
<el-input type="number" v-model="consumer.connections" :placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Async" prop="async" class="dubbo-form-item">
|
||||
<el-select v-model="consumer.async" class="select-100">
|
||||
<el-option v-for="option in asyncOptions" :key="option" :label="option" :value="option"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="LoadBalance" prop="loadBalance" class="dubbo-form-item">
|
||||
<el-select v-model="consumer.loadBalance" class="select-100">
|
||||
<el-option v-for="option in loadBalances" :key="option" :label="option" :value="option"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './dubbo.css'
|
||||
import {ConsumerAndService, RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
|
||||
|
||||
export default {
|
||||
name: "MsDubboConsumerService",
|
||||
props: {
|
||||
description: String,
|
||||
consumer: ConsumerAndService,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
asyncOptions: ConsumerAndService.ASYNC_OPTIONS,
|
||||
loadBalances: ConsumerAndService.LOAD_BALANCE_OPTIONS,
|
||||
methods: [],
|
||||
rules: {
|
||||
version: [
|
||||
{max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'}
|
||||
],
|
||||
cluster: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
],
|
||||
group: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<el-form :model="request" :rules="rules" ref="request" label-width="100px" size="small" v-loading="loading"
|
||||
:disabled="isReadOnly">
|
||||
<el-button class="get-provider" type="primary" size="small" @click="getProviderList">Get Provider List</el-button>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Interfaces" prop="interfaces">
|
||||
<el-select v-model="serviceInterface" class="select-100" @change="changeInterface" :disabled="isDisable">
|
||||
<el-option v-for="i in interfaces" :key="i.value" :label="i.label" :value="i.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Methods" prop="methods">
|
||||
<el-select v-model="method" class="select-100" @change="changeMethod" :disabled="isDisable">
|
||||
<el-option v-for="m in methods" :key="m" :label="m" :value="m"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Interface" prop="interface">
|
||||
<el-input v-model="request.interface" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Method" prop="method">
|
||||
<el-input v-model="request.method" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {DubboRequest, RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
|
||||
|
||||
export default {
|
||||
name: "MsDubboInterface",
|
||||
props: {
|
||||
request: DubboRequest,
|
||||
registryCenter: RegistryCenter,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
providerMap: {},
|
||||
serviceInterface: "",
|
||||
method: "",
|
||||
interfaces: [],
|
||||
methods: [],
|
||||
rules: {
|
||||
interface: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'}
|
||||
],
|
||||
method: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeInterface(value) {
|
||||
this.methods = this.providerMap[value].methods;
|
||||
this.request.interface = value;
|
||||
this.request.consumerAndService.version = this.providerMap[value].version;
|
||||
},
|
||||
changeMethod(value) {
|
||||
this.request.method = value;
|
||||
},
|
||||
getProviderList() {
|
||||
let param = {
|
||||
protocol: this.request.registryCenter.protocol || this.request.dubboConfig.registryCenter.protocol,
|
||||
address: this.request.registryCenter.address || this.request.dubboConfig.registryCenter.address,
|
||||
group: this.request.registryCenter.group || this.request.dubboConfig.registryCenter.group,
|
||||
};
|
||||
|
||||
this.loading = true;
|
||||
this.$post("/api/dubbo/providers", param).then(response => {
|
||||
this.methodMap = {};
|
||||
this.interfaces = [];
|
||||
response.data.data.forEach(p => {
|
||||
this.providerMap[p.serviceInterface] = p;
|
||||
this.interfaces.push({label: p.service, value: p.serviceInterface})
|
||||
});
|
||||
if (this.methodMap[this.request.interface]) {
|
||||
this.methods = this.methodMap[this.request.interface].methods;
|
||||
}
|
||||
this.loading = false;
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
this.$warning(this.$t('api_test.request.dubbo.check_registry_center'));
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isDisable() {
|
||||
return this.interfaces.length === 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.get-provider {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.select-100 {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<el-form :model="registry" :rules="rules" ref="registry" label-width="100px" size="small" :disabled="isReadOnly">
|
||||
<div class="dubbo-form-description" v-if="description">
|
||||
{{description}}
|
||||
</div>
|
||||
<el-form-item label="Protocol" prop="protocol" class="dubbo-form-item">
|
||||
<el-select v-model="registry.protocol" class="select-100">
|
||||
<el-option v-for="p in protocols" :key="p" :label="p" :value="p"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Group" prop="group" class="dubbo-form-item">
|
||||
<el-input v-model="registry.group" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="UserName" prop="username" class="dubbo-form-item">
|
||||
<el-input v-model="registry.username" maxlength="100" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Password" prop="password" class="dubbo-form-item">
|
||||
<el-input v-model="registry.password" maxlength="30" show-word-limit show-password autocomplete="new-password"
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Address" prop="address" class="dubbo-form-item-long">
|
||||
<el-input v-model="registry.address" maxlength="300" show-word-limit
|
||||
:placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Timeout" prop="timeout" class="dubbo-form-item">
|
||||
<el-input type="number" v-model="registry.timeout" :placeholder="$t('commons.input_content')"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './dubbo.css'
|
||||
import {RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
|
||||
|
||||
export default {
|
||||
name: "MsDubboRegistryCenter",
|
||||
props: {
|
||||
description: String,
|
||||
registry: RegistryCenter,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
protocols: RegistryCenter.PROTOCOLS,
|
||||
methods: [],
|
||||
rules: {
|
||||
group: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
],
|
||||
username: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
],
|
||||
password: [
|
||||
{max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'}
|
||||
],
|
||||
address: [
|
||||
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,43 @@
|
|||
@media only screen and (max-width: 1200px) {
|
||||
.dubbo-form-item {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.dubbo-form-item-long {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1201px) and (max-width: 1600px) {
|
||||
.dubbo-form-item {
|
||||
width: 33.33%;
|
||||
}
|
||||
|
||||
.dubbo-form-item-long {
|
||||
width: 66.67%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1601px) {
|
||||
.dubbo-form-item {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.dubbo-form-item-long {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.dubbo-form-item, .dubbo-form-item-long {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dubbo-form-description {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
}
|
|
@ -220,6 +220,60 @@ export class ThreadGroup extends DefaultTestElement {
|
|||
}
|
||||
}
|
||||
|
||||
export class DubboSample extends DefaultTestElement {
|
||||
constructor(testName, request = {}) {
|
||||
super('io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample',
|
||||
'io.github.ningyu.jmeter.plugin.dubbo.gui.DubboSampleGui',
|
||||
'io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample', testName);
|
||||
this.request = request;
|
||||
|
||||
this.stringProp("FIELD_DUBBO_CONFIG_CENTER_PROTOCOL", this.request.configCenter.protocol);
|
||||
this.stringProp("FIELD_DUBBO_CONFIG_CENTER_GROUP", this.request.configCenter.group);
|
||||
this.stringProp("FIELD_DUBBO_CONFIG_CENTER_NAMESPACE", this.request.configCenter.namespace);
|
||||
this.stringProp("FIELD_DUBBO_CONFIG_CENTER_USER_NAME", this.request.configCenter.username);
|
||||
this.stringProp("FIELD_DUBBO_CONFIG_CENTER_PASSWORD", this.request.configCenter.password);
|
||||
this.stringProp("FIELD_DUBBO_CONFIG_CENTER_ADDRESS", this.request.configCenter.address);
|
||||
this.stringProp("FIELD_DUBBO_CONFIG_CENTER_TIMEOUT", this.request.configCenter.timeout);
|
||||
|
||||
this.stringProp("FIELD_DUBBO_REGISTRY_PROTOCOL", this.request.registryCenter.protocol);
|
||||
this.stringProp("FIELD_DUBBO_REGISTRY_GROUP", this.request.registryCenter.group);
|
||||
this.stringProp("FIELD_DUBBO_REGISTRY_USER_NAME", this.request.registryCenter.username);
|
||||
this.stringProp("FIELD_DUBBO_REGISTRY_PASSWORD", this.request.registryCenter.password);
|
||||
this.stringProp("FIELD_DUBBO_ADDRESS", this.request.registryCenter.address);
|
||||
this.stringProp("FIELD_DUBBO_REGISTRY_TIMEOUT", this.request.registryCenter.timeout);
|
||||
|
||||
this.stringProp("FIELD_DUBBO_TIMEOUT", this.request.consumerAndService.timeout);
|
||||
this.stringProp("FIELD_DUBBO_VERSION", this.request.consumerAndService.version);
|
||||
this.stringProp("FIELD_DUBBO_RETRIES", this.request.consumerAndService.retries);
|
||||
this.stringProp("FIELD_DUBBO_GROUP", this.request.consumerAndService.group);
|
||||
this.stringProp("FIELD_DUBBO_CONNECTIONS", this.request.consumerAndService.connections);
|
||||
this.stringProp("FIELD_DUBBO_LOADBALANCE", this.request.consumerAndService.loadBalance);
|
||||
this.stringProp("FIELD_DUBBO_ASYNC", this.request.consumerAndService.async);
|
||||
this.stringProp("FIELD_DUBBO_CLUSTER", this.request.consumerAndService.cluster);
|
||||
|
||||
this.stringProp("FIELD_DUBBO_RPC_PROTOCOL", this.request.protocol);
|
||||
this.stringProp("FIELD_DUBBO_INTERFACE", this.request.interface);
|
||||
this.stringProp("FIELD_DUBBO_METHOD", this.request.method);
|
||||
|
||||
this.intProp("FIELD_DUBBO_METHOD_ARGS_SIZE", this.request.args.length);
|
||||
this.intProp("FIELD_DUBBO_ATTACHMENT_ARGS_SIZE", this.request.attachmentArgs.length);
|
||||
this.request.args.forEach((arg, i) => {
|
||||
if (!!arg.name || !!arg.value) {
|
||||
let index = i + 1;
|
||||
this.stringProp("FIELD_DUBBO_METHOD_ARGS_PARAM_TYPE" + index, arg.name);
|
||||
this.stringProp("FIELD_DUBBO_METHOD_ARGS_PARAM_VALUE" + index, arg.value);
|
||||
}
|
||||
})
|
||||
this.request.attachmentArgs.forEach((arg, i) => {
|
||||
if (!!arg.name || !!arg.value) {
|
||||
let index = i + 1;
|
||||
this.stringProp("FIELD_DUBBO_ATTACHMENT_ARGS_KEY" + index, arg.name);
|
||||
this.stringProp("FIELD_DUBBO_ATTACHMENT_ARGS_VALUE" + index, arg.value);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class HTTPSamplerProxy extends DefaultTestElement {
|
||||
constructor(testName, request) {
|
||||
super('HTTPSamplerProxy', 'HttpTestSampleGui', 'HTTPSamplerProxy', testName);
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
ResponseCodeAssertion,
|
||||
ResponseDataAssertion,
|
||||
ResponseHeadersAssertion,
|
||||
RegexExtractor, JSONPostProcessor, XPath2Extractor,
|
||||
RegexExtractor, JSONPostProcessor, XPath2Extractor, DubboSample,
|
||||
} from "./JMX";
|
||||
|
||||
export const uuid = function () {
|
||||
|
@ -74,7 +74,7 @@ export class BaseConfig {
|
|||
if (types) {
|
||||
for (let name in types) {
|
||||
if (types.hasOwnProperty(name) && options.hasOwnProperty(name)) {
|
||||
options[name].forEach((o) => {
|
||||
options[name].forEach(o => {
|
||||
this[name].push(new types[name](o));
|
||||
})
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ export class Test extends BaseConfig {
|
|||
constructor(options) {
|
||||
super();
|
||||
this.type = "MS API CONFIG";
|
||||
this.version = '1.0.0';
|
||||
this.version = '1.1.0';
|
||||
this.id = uuid();
|
||||
this.name = undefined;
|
||||
this.projectId = undefined;
|
||||
|
@ -152,7 +152,7 @@ export class Test extends BaseConfig {
|
|||
}
|
||||
|
||||
export class Scenario extends BaseConfig {
|
||||
constructor(options) {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.name = undefined;
|
||||
this.url = undefined;
|
||||
|
@ -160,15 +160,17 @@ export class Scenario extends BaseConfig {
|
|||
this.headers = [];
|
||||
this.requests = [];
|
||||
this.environmentId = undefined;
|
||||
this.dubboConfig = undefined;
|
||||
this.environment = undefined;
|
||||
|
||||
this.set(options);
|
||||
this.sets({variables: KeyValue, headers: KeyValue, requests: Request}, options);
|
||||
this.sets({variables: KeyValue, headers: KeyValue, requests: RequestFactory}, options);
|
||||
}
|
||||
|
||||
initOptions(options) {
|
||||
options = options || {};
|
||||
options.requests = options.requests || [new Request()];
|
||||
options.requests = options.requests || [new RequestFactory()];
|
||||
options.dubboConfig = new DubboConfig(options.dubboConfig);
|
||||
return options;
|
||||
}
|
||||
|
||||
|
@ -187,9 +189,50 @@ export class Scenario extends BaseConfig {
|
|||
}
|
||||
}
|
||||
|
||||
export class Request extends BaseConfig {
|
||||
constructor(options) {
|
||||
class DubboConfig extends BaseConfig {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.configCenter = new ConfigCenter(options.configCenter)
|
||||
this.registryCenter = new RegistryCenter(options.registryCenter)
|
||||
this.consumerAndService = new ConsumerAndService(options.consumerAndService)
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestFactory {
|
||||
static TYPES = {
|
||||
HTTP: "HTTP",
|
||||
DUBBO: "DUBBO",
|
||||
}
|
||||
|
||||
constructor(options = {}) {
|
||||
options.type = options.type || RequestFactory.TYPES.HTTP
|
||||
switch (options.type) {
|
||||
case RequestFactory.TYPES.DUBBO:
|
||||
return new DubboRequest(options);
|
||||
default:
|
||||
return new HttpRequest(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Request extends BaseConfig {
|
||||
constructor(type) {
|
||||
super();
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
showType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
showMethod() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export class HttpRequest extends Request {
|
||||
constructor(options) {
|
||||
super(RequestFactory.TYPES.HTTP);
|
||||
this.name = undefined;
|
||||
this.url = undefined;
|
||||
this.path = undefined;
|
||||
|
@ -238,6 +281,153 @@ export class Request extends BaseConfig {
|
|||
isValid: true
|
||||
}
|
||||
}
|
||||
|
||||
showType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
showMethod() {
|
||||
return this.method.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
export class DubboRequest extends Request {
|
||||
static PROTOCOLS = {
|
||||
DUBBO: "dubbo://",
|
||||
RMI: "rmi://",
|
||||
}
|
||||
|
||||
constructor(options = {}) {
|
||||
super(RequestFactory.TYPES.DUBBO);
|
||||
this.name = options.name;
|
||||
this.protocol = options.protocol || DubboRequest.PROTOCOLS.DUBBO;
|
||||
this.interface = options.interface;
|
||||
this.method = options.method;
|
||||
this.configCenter = new ConfigCenter(options.configCenter);
|
||||
this.registryCenter = new RegistryCenter(options.registryCenter);
|
||||
this.consumerAndService = new ConsumerAndService(options.consumerAndService);
|
||||
this.args = [];
|
||||
this.attachmentArgs = [];
|
||||
this.assertions = new Assertions(options.assertions);
|
||||
this.extract = new Extract(options.extract);
|
||||
// Scenario.dubboConfig
|
||||
this.dubboConfig = undefined;
|
||||
|
||||
this.sets({args: KeyValue, attachmentArgs: KeyValue}, options);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
if (!this.interface) {
|
||||
return {
|
||||
isValid: false,
|
||||
info: 'api_test.request.dubbo.input_interface'
|
||||
}
|
||||
}
|
||||
if (!this.method) {
|
||||
return {
|
||||
isValid: false,
|
||||
info: 'api_test.request.dubbo.input_method'
|
||||
}
|
||||
}
|
||||
if (!this.configCenter.isValid()) {
|
||||
return {
|
||||
isValid: false,
|
||||
info: 'api_test.request.dubbo.input_config_center'
|
||||
}
|
||||
}
|
||||
if (!this.registryCenter.isValid()) {
|
||||
return {
|
||||
isValid: false,
|
||||
info: 'api_test.request.dubbo.input_registry_center'
|
||||
}
|
||||
}
|
||||
if (!this.consumerAndService.isValid()) {
|
||||
return {
|
||||
isValid: false,
|
||||
info: 'api_test.request.dubbo.input_consumer_service'
|
||||
}
|
||||
}
|
||||
return {
|
||||
isValid: true
|
||||
}
|
||||
}
|
||||
|
||||
showType() {
|
||||
return "RPC";
|
||||
}
|
||||
|
||||
showMethod() {
|
||||
// dubbo:// -> DUBBO
|
||||
return this.protocol.substr(0, this.protocol.length - 3).toUpperCase();
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new DubboRequest(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigCenter extends BaseConfig {
|
||||
static PROTOCOLS = ["zookeeper", "nacos", "apollo"];
|
||||
|
||||
constructor(options) {
|
||||
super();
|
||||
this.protocol = undefined;
|
||||
this.group = undefined;
|
||||
this.namespace = undefined;
|
||||
this.username = undefined;
|
||||
this.address = undefined;
|
||||
this.password = undefined;
|
||||
this.timeout = undefined;
|
||||
|
||||
this.set(options);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return !!this.protocol || !!this.group || !!this.namespace || !!this.username || !!this.address || !!this.password || !!this.timeout;
|
||||
}
|
||||
}
|
||||
|
||||
export class RegistryCenter extends BaseConfig {
|
||||
static PROTOCOLS = ["none", "zookeeper", "nacos", "apollo", "multicast", "redis", "simple"];
|
||||
|
||||
constructor(options) {
|
||||
super();
|
||||
this.protocol = undefined;
|
||||
this.group = undefined;
|
||||
this.username = undefined;
|
||||
this.address = undefined;
|
||||
this.password = undefined;
|
||||
this.timeout = undefined;
|
||||
|
||||
this.set(options);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return !!this.protocol || !!this.group || !!this.username || !!this.address || !!this.password || !!this.timeout;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConsumerAndService extends BaseConfig {
|
||||
static ASYNC_OPTIONS = ["sync", "async"];
|
||||
static LOAD_BALANCE_OPTIONS = ["random", "roundrobin", "leastactive", "consistenthash"];
|
||||
|
||||
constructor(options) {
|
||||
super();
|
||||
this.timeout = "1000";
|
||||
this.version = "1.0";
|
||||
this.retries = "0";
|
||||
this.cluster = "failfast";
|
||||
this.group = undefined;
|
||||
this.connections = "100";
|
||||
this.async = "sync";
|
||||
this.loadBalance = "random";
|
||||
|
||||
this.set(options);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return !!this.timeout || !!this.version || !!this.retries || !!this.cluster || !!this.group || !!this.connections || !!this.async || !!this.loadBalance;
|
||||
}
|
||||
}
|
||||
|
||||
export class Body extends BaseConfig {
|
||||
|
@ -423,9 +613,9 @@ const JMX_ASSERTION_CONDITION = {
|
|||
OR: 1 << 5
|
||||
}
|
||||
|
||||
class JMXRequest {
|
||||
class JMXHttpRequest {
|
||||
constructor(request, environment) {
|
||||
if (request && request instanceof Request && (request.url || request.path)) {
|
||||
if (request && request instanceof HttpRequest && (request.url || request.path)) {
|
||||
this.useEnvironment = request.useEnvironment;
|
||||
this.method = request.method;
|
||||
if (!request.useEnvironment) {
|
||||
|
@ -457,7 +647,37 @@ class JMXRequest {
|
|||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
class JMXDubboRequest {
|
||||
constructor(request, dubboConfig) {
|
||||
// Request 复制
|
||||
let obj = request.clone();
|
||||
// 去掉无效的kv
|
||||
obj.args = obj.args.filter(arg => {
|
||||
return arg.isValid();
|
||||
});
|
||||
obj.attachmentArgs = obj.attachmentArgs.filter(arg => {
|
||||
return arg.isValid();
|
||||
});
|
||||
|
||||
// Scenario DubboConfig复制
|
||||
this.copy(obj.configCenter, dubboConfig.configCenter);
|
||||
this.copy(obj.registryCenter, dubboConfig.registryCenter);
|
||||
this.copy(obj.consumerAndService, dubboConfig.consumerAndService);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
copy(target, source) {
|
||||
for (let key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
if (source[key] !== undefined && !target[key]) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JMeterTestPlan extends Element {
|
||||
|
@ -499,22 +719,27 @@ class JMXGenerator {
|
|||
|
||||
scenario.requests.forEach(request => {
|
||||
if (!request.isValid()) return;
|
||||
let sampler;
|
||||
|
||||
let httpSamplerProxy = new HTTPSamplerProxy(request.name || "", new JMXRequest(request, scenario.environment));
|
||||
|
||||
this.addRequestHeader(httpSamplerProxy, request);
|
||||
|
||||
if (request.method.toUpperCase() === 'GET') {
|
||||
this.addRequestArguments(httpSamplerProxy, request);
|
||||
} else {
|
||||
this.addRequestBody(httpSamplerProxy, request);
|
||||
if (request instanceof DubboRequest) {
|
||||
sampler = new DubboSample(request.name || "", new JMXDubboRequest(request, scenario.dubboConfig));
|
||||
}
|
||||
|
||||
this.addRequestAssertion(httpSamplerProxy, request);
|
||||
if (request instanceof HttpRequest) {
|
||||
sampler = new HTTPSamplerProxy(request.name || "", new JMXHttpRequest(request, scenario.environment));
|
||||
this.addRequestHeader(sampler, request);
|
||||
if (request.method.toUpperCase() === 'GET') {
|
||||
this.addRequestArguments(sampler, request);
|
||||
} else {
|
||||
this.addRequestBody(sampler, request);
|
||||
}
|
||||
}
|
||||
|
||||
this.addRequestExtractor(httpSamplerProxy, request);
|
||||
this.addRequestAssertion(sampler, request);
|
||||
|
||||
threadGroup.put(httpSamplerProxy);
|
||||
this.addRequestExtractor(sampler, request);
|
||||
|
||||
threadGroup.put(sampler);
|
||||
})
|
||||
|
||||
testPlan.put(threadGroup);
|
||||
|
|
|
@ -51,9 +51,11 @@
|
|||
callback(new Error(this.$t('commons.input_content')));
|
||||
} else if (!cronValidate(cronValue)) {
|
||||
callback(new Error(this.$t('schedule.cron_expression_format_error')));
|
||||
} else if(!this.intervalShortValidate()) {
|
||||
callback(new Error(this.$t('schedule.cron_expression_interval_short_error')));
|
||||
} else if (!customValidate.pass){
|
||||
}
|
||||
// else if(!this.intervalShortValidate()) {
|
||||
// callback(new Error(this.$t('schedule.cron_expression_interval_short_error')));
|
||||
// }
|
||||
else if (!customValidate.pass){
|
||||
callback(new Error(customValidate.info));
|
||||
} else {
|
||||
callback();
|
||||
|
@ -87,6 +89,7 @@
|
|||
saveCron () {
|
||||
this.$refs['from'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.intervalShortValidate();
|
||||
this.save(this.form.cronValue);
|
||||
this.dialogVisible = false;
|
||||
} else {
|
||||
|
@ -103,8 +106,9 @@
|
|||
}
|
||||
},
|
||||
intervalShortValidate() {
|
||||
if (this.getIntervalTime() < 5*60*1000) {
|
||||
return false;
|
||||
if (this.getIntervalTime() < 3*60*1000) {
|
||||
// return false;
|
||||
this.$info(this.$t('schedule.cron_expression_interval_short_error'));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
|
|
@ -24,11 +24,11 @@
|
|||
return {
|
||||
dataMap: new Map([
|
||||
["Pass", {name: this.$t('test_track.plan_view.pass'), itemStyle: {color: '#67C23A'}}],
|
||||
["Failure", {name: this.$t('test_track.plan_view.failure'), itemStyle: {color: '#F56C6C'}}],
|
||||
["Blocking", {name: this.$t('test_track.plan_view.blocking'), itemStyle: {color: '#E6A23C'}}],
|
||||
["Skip", {name: this.$t('test_track.plan_view.skip'), itemStyle: {color: '#909399'}}],
|
||||
["Prepare", {name: this.$t('test_track.plan.plan_status_prepare'), itemStyle: {color: '#DEDE10'}}],
|
||||
["Failure", {name: this.$t('test_track.plan_view.failure'), itemStyle: {color: '#F56C6C'}}],
|
||||
["Underway", {name: this.$t('test_track.plan.plan_status_running'), itemStyle: {color: 'lightskyblue'}}]
|
||||
["Underway", {name: this.$t('test_track.plan.plan_status_running'), itemStyle: {color: 'lightskyblue'}}],
|
||||
["Prepare", {name: this.$t('test_track.plan.plan_status_prepare'), itemStyle: {color: '#DEDE10'}}]
|
||||
]),
|
||||
charData: [],
|
||||
isShow: true
|
||||
|
@ -40,11 +40,11 @@
|
|||
default() {
|
||||
return [
|
||||
{status: 'Pass', count: '235'},
|
||||
{status: 'Failure', count: '310'},
|
||||
{status: 'Blocking', count: '274'},
|
||||
{status: 'Skip', count: '335'},
|
||||
{status: 'Prepare', count: '265'},
|
||||
{status: 'Failure', count: '310'},
|
||||
{status: 'Underway', count: '245'},
|
||||
{status: 'Prepare', count: '265'},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ export default {
|
|||
result.loading = false;
|
||||
window.console.error(error.response || error.message);
|
||||
if (error.response && error.response.data) {
|
||||
if (error.response.headers["authentication-status"] != "invalid") {
|
||||
if (error.response.headers["authentication-status"] !== "invalid") {
|
||||
Message.error({message: error.response.data.message, showClose: true});
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -19,7 +19,7 @@ export const ZH_CN = 'zh_CN';
|
|||
export const ZH_TW = 'zh_TW';
|
||||
export const EN_US = 'en_US';
|
||||
|
||||
export const requestHeaders = [
|
||||
export const REQUEST_HEADERS = [
|
||||
{value: 'Accept'},
|
||||
{value: 'Accept-Charset'},
|
||||
{value: 'Accept-Language'},
|
||||
|
|
|
@ -350,6 +350,7 @@ export default {
|
|||
please_save_test: "Please Save Test First",
|
||||
},
|
||||
scenario: {
|
||||
dubbo: "Dubbo Config",
|
||||
config: "Scenario Config",
|
||||
input_name: "Please enter the scenario name",
|
||||
name: "Scenario Name",
|
||||
|
@ -409,6 +410,15 @@ export default {
|
|||
regex_expression: "Regular expression",
|
||||
json_path_expression: "JSONPath expression",
|
||||
xpath_expression: "XPath expression",
|
||||
},
|
||||
dubbo: {
|
||||
protocol: "protocol",
|
||||
input_interface: "Please enter the interface",
|
||||
input_method: "Please enter the method",
|
||||
input_config_center: "Please enter the config center",
|
||||
input_registry_center: "Please enter the registry center",
|
||||
input_consumer_service: "Please enter the consumer & service",
|
||||
check_registry_center: "Can't get interface list, please check the registry center",
|
||||
}
|
||||
},
|
||||
api_import: {
|
||||
|
@ -418,8 +428,8 @@ export default {
|
|||
file_size_limit: "The file size does not exceed 20 M",
|
||||
tip: "Instructions",
|
||||
export_tip: "Export Tip",
|
||||
ms_tip: "Support for Metersphere JSON format",
|
||||
ms_export_tip: "Export jSON-formatted files via Metersphere website or browser plug-ins",
|
||||
ms_tip: "Support for MeterSphere JSON format",
|
||||
ms_export_tip: "Export jSON-formatted files via MeterSphere website or browser plug-ins",
|
||||
swagger_tip: "Only Swagger2.x json files are supported",
|
||||
postman_tip: "Only Postman Collection V2.1 json files are supported",
|
||||
postman_export_tip: "Export the test collection by Postman",
|
||||
|
@ -686,7 +696,7 @@ export default {
|
|||
please_input_cron_expression: "Please Input Cron Expression",
|
||||
generate_expression: "Generate Expression",
|
||||
cron_expression_format_error: "Cron Expression Format Error",
|
||||
cron_expression_interval_short_error: "Interval Time Should Longer than 5 Minutes",
|
||||
cron_expression_interval_short_error: "Interval time shorter than 3 minutes, please avoid running tests that take too long",
|
||||
cron: {
|
||||
seconds: "Seconds",
|
||||
minutes: "Minutes",
|
||||
|
|
|
@ -349,6 +349,7 @@ export default {
|
|||
please_save_test: "请先保存测试",
|
||||
},
|
||||
scenario: {
|
||||
dubbo: "Dubbo配置",
|
||||
config: "场景配置",
|
||||
input_name: "请输入场景名称",
|
||||
name: "场景名称",
|
||||
|
@ -408,6 +409,16 @@ export default {
|
|||
regex_expression: "Perl型正则表达式",
|
||||
json_path_expression: "JSONPath表达式",
|
||||
xpath_expression: "XPath表达式",
|
||||
},
|
||||
dubbo: {
|
||||
protocol: "协议",
|
||||
input_interface: "请输入Interface",
|
||||
input_method: "请输入Method",
|
||||
input_config_center: "请输入Config Center",
|
||||
input_registry_center: "请输入Registry Center",
|
||||
input_consumer_service: "请输入Consumer & Service",
|
||||
check_registry_center: "获取失败,请检查Registry Center",
|
||||
form_description: "如果当前配置项无值,则取场景配置项的值",
|
||||
}
|
||||
},
|
||||
api_import: {
|
||||
|
@ -685,7 +696,7 @@ export default {
|
|||
please_input_cron_expression: "请输入 Cron 表达式",
|
||||
generate_expression: "生成表达式",
|
||||
cron_expression_format_error: "Cron 表达式格式错误",
|
||||
cron_expression_interval_short_error: "间隔时间请大于 5 分钟",
|
||||
cron_expression_interval_short_error: "间隔时间小于 3 分钟, 请避免执行耗时过长的测试",
|
||||
cron: {
|
||||
seconds: "秒",
|
||||
minutes: "分钟",
|
||||
|
|
|
@ -348,6 +348,7 @@ export default {
|
|||
please_save_test: "請先保存測試",
|
||||
},
|
||||
scenario: {
|
||||
dubbo: "Dubbo配寘",
|
||||
creator: "創建人",
|
||||
config: "場景配寘",
|
||||
input_name: "請輸入場景名稱",
|
||||
|
@ -408,6 +409,15 @@ export default {
|
|||
regex_expression: "Perl型規則運算式",
|
||||
json_path_expression: "JSONPath運算式",
|
||||
xpath_expression: "XPath運算式",
|
||||
},
|
||||
dubbo: {
|
||||
protocol: "協定",
|
||||
input_interface: "請輸入Interface",
|
||||
input_method: "請輸入Method",
|
||||
input_config_center: "請輸入Config Center",
|
||||
input_registry_center: "請輸入Registry Center",
|
||||
input_consumer_service: "請輸入Consumer & Service",
|
||||
check_registry_center: "獲取失敗,請檢查Registry Center",
|
||||
}
|
||||
},
|
||||
api_import: {
|
||||
|
@ -417,8 +427,8 @@ export default {
|
|||
file_size_limit: "文件大小不超過 20 M",
|
||||
tip: "說明",
|
||||
export_tip: "導出方法",
|
||||
ms_tip: "支持 Metersphere json 格式",
|
||||
ms_export_tip: "通過 Metersphere Api 測試頁面或者瀏覽器插件導出 json 格式文件",
|
||||
ms_tip: "支持 MeterSphere json 格式",
|
||||
ms_export_tip: "通過 MeterSphere Api 測試頁面或者瀏覽器插件導出 json 格式文件",
|
||||
postman_tip: "只支持 Postman Collection v2.1 格式的 json 文件",
|
||||
swagger_tip: "只支持 Swagger2.x 版本的 json 文件",
|
||||
post_export_tip: "通過 Postman 導出測試集合",
|
||||
|
@ -684,7 +694,7 @@ export default {
|
|||
please_input_cron_expression: "請輸入 Cron 表達式",
|
||||
generate_expression: "生成表達式",
|
||||
cron_expression_format_error: "Cron 表達式格式錯誤",
|
||||
cron_expression_interval_short_error: "間隔時間請大於 5 分鐘",
|
||||
cron_expression_interval_short_error: "間隔時間小於 3 分鐘, 請避免執行耗時過長的測試",
|
||||
cron: {
|
||||
seconds: "秒",
|
||||
minutes: "分鐘",
|
||||
|
|
Loading…
Reference in New Issue