Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
f02477ce05
|
@ -14,7 +14,6 @@ import io.metersphere.api.service.*;
|
|||
import io.metersphere.base.domain.ApiTest;
|
||||
import io.metersphere.base.domain.Schedule;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import io.metersphere.commons.constants.ScheduleGroup;
|
||||
import io.metersphere.commons.utils.CronUtils;
|
||||
import io.metersphere.commons.utils.PageUtils;
|
||||
import io.metersphere.commons.utils.Pager;
|
||||
|
@ -59,7 +58,7 @@ public class APITestController {
|
|||
@Resource
|
||||
private ScheduleService scheduleService;
|
||||
@Resource
|
||||
private APIReportService apiReportService;
|
||||
private HistoricalDataUpgradeService historicalDataUpgradeService;
|
||||
|
||||
@GetMapping("recent/{count}")
|
||||
public List<APITestResult> recentTest(@PathVariable int count) {
|
||||
|
@ -359,4 +358,8 @@ public class APITestController {
|
|||
schedule.setEnable(request.isEnable());
|
||||
apiAutomationService.updateSchedule(schedule);
|
||||
}
|
||||
@PostMapping(value = "/historicalDataUpgrade")
|
||||
public String historicalDataUpgrade(@RequestBody SaveHistoricalDataUpgrade request) {
|
||||
return historicalDataUpgradeService.upgrade(request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,5 +121,6 @@ public class ApiAutomationController {
|
|||
public void createSchedule(@RequestBody Schedule request) {
|
||||
apiAutomationService.createSchedule(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class SaveHistoricalDataUpgrade {
|
||||
private List<String> testIds;
|
||||
|
||||
private String projectId;
|
||||
|
||||
private String modulePath;
|
||||
|
||||
private String moduleId;
|
||||
}
|
|
@ -31,7 +31,7 @@ public class MsDubboSampler extends MsTestElement {
|
|||
private String type = "DubboSampler";
|
||||
|
||||
@JSONField(ordinal = 52)
|
||||
private String protocol;
|
||||
private String protocol = "DUBBO";
|
||||
@JsonProperty(value = "interface")
|
||||
@JSONField(ordinal = 53, name = "interface")
|
||||
private String _interface;
|
||||
|
|
|
@ -172,7 +172,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
LogUtil.error(e);
|
||||
}
|
||||
// REST参数
|
||||
if (CollectionUtils.isNotEmpty(this.getArguments())) {
|
||||
if (CollectionUtils.isNotEmpty(this.getRest())) {
|
||||
sampler.setArguments(httpArguments(this.getRest()));
|
||||
}
|
||||
// 请求参数
|
||||
|
@ -186,9 +186,11 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
if (StringUtils.isNotEmpty(this.body.getType()) && this.body.getType().equals("Form Data")) {
|
||||
sampler.setDoMultipart(true);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(bodyParams)) {
|
||||
sampler.setArguments(httpArguments(bodyParams));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final HashTree httpSamplerTree = tree.add(sampler);
|
||||
// 通用请求Headers
|
||||
|
|
|
@ -42,7 +42,11 @@ public class Body {
|
|||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
public boolean isOldKV() {
|
||||
if (StringUtils.equals(type, KV)) {
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
public List<KeyValue> getBodyParams(HTTPSamplerProxy sampler, String requestId) {
|
||||
List<KeyValue> body = new ArrayList<>();
|
||||
if (this.isKV() || this.isBinary()) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.metersphere.api.parse;
|
|||
|
||||
import io.metersphere.api.dto.ApiTestImportRequest;
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionResult;
|
||||
import io.metersphere.api.dto.definition.ApiModuleDTO;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
|
@ -11,6 +12,8 @@ import io.metersphere.api.dto.scenario.request.RequestType;
|
|||
import io.metersphere.api.service.ApiModuleService;
|
||||
import io.metersphere.base.domain.ApiModule;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -60,6 +63,32 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
|
|||
}
|
||||
}
|
||||
|
||||
protected ApiModule getSelectModule(String moduleId) {
|
||||
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
|
||||
if (StringUtils.isNotBlank(moduleId)) {
|
||||
ApiModule module = new ApiModule();
|
||||
ApiModuleDTO moduleDTO = apiModuleService.getNode(moduleId);
|
||||
BeanUtils.copyBean(module, moduleDTO);
|
||||
return module;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected ApiModule buildModule(ApiModule parentModule, String name, boolean isSaved) {
|
||||
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
|
||||
ApiModule module;
|
||||
if (parentModule != null) {
|
||||
module = apiModuleService.getNewModule(name, this.projectId, parentModule.getLevel() + 1);
|
||||
module.setParentId(parentModule.getId());
|
||||
} else {
|
||||
module = apiModuleService.getNewModule(name, this.projectId, 1);
|
||||
}
|
||||
if (isSaved) {
|
||||
createModule(module);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
protected void createModule(ApiModule module) {
|
||||
module.setProtocol(RequestType.HTTP);
|
||||
List<ApiModule> apiModules = apiModuleService.selectSameModule(module);
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject;
|
|||
import com.alibaba.fastjson.parser.Feature;
|
||||
import io.metersphere.api.dto.ApiTestImportRequest;
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionResult;
|
||||
import io.metersphere.api.dto.definition.ApiModuleDTO;
|
||||
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
|
@ -31,7 +32,7 @@ public class MsParser extends ApiImportAbstractParser {
|
|||
if (testObject.get("projectName") != null) {
|
||||
return parseMsFormat(testStr, request);
|
||||
} else {
|
||||
return parsePluginFormat(testObject, request.isSaved());
|
||||
return parsePluginFormat(testObject, request);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +45,7 @@ public class MsParser extends ApiImportAbstractParser {
|
|||
if (StringUtils.isBlank(apiDefinition.getModulePath())) {
|
||||
apiDefinition.setModuleId(null);
|
||||
}
|
||||
parseModule(apiDefinition, importRequest.isSaved());
|
||||
parseModule(apiDefinition, importRequest);
|
||||
apiDefinition.setId(id);
|
||||
apiDefinition.setProjectId(this.projectId);
|
||||
String request = apiDefinition.getRequest();
|
||||
|
@ -55,16 +56,16 @@ public class MsParser extends ApiImportAbstractParser {
|
|||
return apiDefinitionImport;
|
||||
}
|
||||
|
||||
private ApiDefinitionImport parsePluginFormat(JSONObject testObject, boolean isSaved) {
|
||||
private ApiDefinitionImport parsePluginFormat(JSONObject testObject, ApiTestImportRequest importRequest) {
|
||||
List<ApiDefinitionResult> results = new ArrayList<>();
|
||||
ApiDefinitionImport apiImport = new ApiDefinitionImport();
|
||||
apiImport.setProtocol(RequestType.HTTP);
|
||||
apiImport.setData(results);
|
||||
testObject.keySet().forEach(tag -> {
|
||||
ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1);
|
||||
if (isSaved) {
|
||||
createModule(module);
|
||||
}
|
||||
|
||||
ApiModule parentModule = getSelectModule(importRequest.getModuleId());
|
||||
ApiModule module = buildModule(parentModule, tag, importRequest.isSaved());
|
||||
|
||||
JSONObject requests = testObject.getJSONObject(tag);
|
||||
requests.keySet().forEach(requestName -> {
|
||||
|
||||
|
@ -125,7 +126,7 @@ public class MsParser extends ApiImportAbstractParser {
|
|||
}
|
||||
|
||||
|
||||
private void parseModule(ApiDefinitionResult apiDefinition, Boolean isSaved) {
|
||||
private void parseModule(ApiDefinitionResult apiDefinition, ApiTestImportRequest importRequest) {
|
||||
String modulePath = apiDefinition.getModulePath();
|
||||
if (StringUtils.isBlank(modulePath)) {
|
||||
return;
|
||||
|
@ -137,29 +138,14 @@ public class MsParser extends ApiImportAbstractParser {
|
|||
modulePath = modulePath.substring(0, modulePath.length() - 1);
|
||||
}
|
||||
List<String> modules = Arrays.asList(modulePath.split("/"));
|
||||
ApiModule parent = null;
|
||||
ApiModule parent = getSelectModule(importRequest.getModuleId());
|
||||
Iterator<String> iterator = modules.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
String item = iterator.next();
|
||||
parent = buildModule(item, parent, isSaved);
|
||||
parent = buildModule(parent, item, importRequest.isSaved());
|
||||
if (!iterator.hasNext()) {
|
||||
apiDefinition.setModuleId(parent.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApiModule buildModule(String name, ApiModule parentModule, boolean isSaved) {
|
||||
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
|
||||
ApiModule module;
|
||||
if (parentModule != null) {
|
||||
module = apiModuleService.getNewModule(name, this.projectId, parentModule.getLevel() + 1);
|
||||
module.setParentId(parentModule.getId());
|
||||
} else {
|
||||
module = apiModuleService.getNewModule(name, this.projectId, 1);
|
||||
}
|
||||
if (isSaved) {
|
||||
createModule(module);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,9 @@ import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
|||
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.service.ApiModuleService;
|
||||
import io.metersphere.base.domain.ApiModule;
|
||||
import io.metersphere.commons.constants.MsRequestBodyType;
|
||||
import io.metersphere.commons.constants.PostmanRequestBodyMode;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
@ -30,7 +28,7 @@ public class PostmanParser extends ApiImportAbstractParser {
|
|||
List<PostmanKeyValue> variables = postmanCollection.getVariable();
|
||||
ApiDefinitionImport apiImport = new ApiDefinitionImport();
|
||||
List<ApiDefinitionResult> results = new ArrayList<>();
|
||||
parseItem(postmanCollection.getItem(), variables, results, buildModule(postmanCollection.getInfo().getName(), null, request.isSaved()), request.isSaved());
|
||||
parseItem(postmanCollection.getItem(), variables, results, buildModule(getSelectModule(request.getModuleId()), postmanCollection.getInfo().getName(), request.isSaved()), request.isSaved());
|
||||
apiImport.setData(results);
|
||||
return apiImport;
|
||||
}
|
||||
|
@ -39,7 +37,7 @@ public class PostmanParser extends ApiImportAbstractParser {
|
|||
for (PostmanItem item : items) {
|
||||
List<PostmanItem> childItems = item.getItem();
|
||||
if (childItems != null) {
|
||||
ApiModule module = buildModule(item.getName(), parentModule, isSaved);
|
||||
ApiModule module = buildModule(parentModule, item.getName() , isSaved);
|
||||
parseItem(childItems, variables, results, module, isSaved);
|
||||
} else {
|
||||
ApiDefinitionResult request = parsePostman(item);
|
||||
|
@ -53,21 +51,6 @@ public class PostmanParser extends ApiImportAbstractParser {
|
|||
}
|
||||
}
|
||||
|
||||
private ApiModule buildModule(String name, ApiModule parentModule, boolean isSaved) {
|
||||
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
|
||||
ApiModule module;
|
||||
if (parentModule != null) {
|
||||
module = apiModuleService.getNewModule(name, this.projectId, parentModule.getLevel() + 1);
|
||||
module.setParentId(parentModule.getId());
|
||||
} else {
|
||||
module = apiModuleService.getNewModule(name, this.projectId, 1);
|
||||
}
|
||||
if (isSaved) {
|
||||
createModule(module);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
private ApiDefinitionResult parsePostman(PostmanItem requestItem) {
|
||||
PostmanRequest requestDesc = requestItem.getRequest();
|
||||
if (requestDesc == null) {
|
||||
|
|
|
@ -11,10 +11,8 @@ import io.metersphere.api.dto.definition.response.HttpResponse;
|
|||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.request.RequestType;
|
||||
import io.metersphere.api.service.ApiModuleService;
|
||||
import io.metersphere.base.domain.ApiModule;
|
||||
import io.metersphere.commons.constants.SwaggerParameterType;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.swagger.models.*;
|
||||
import io.swagger.models.parameters.*;
|
||||
import io.swagger.models.properties.*;
|
||||
|
@ -25,7 +23,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
public class Swagger2Parser extends ApiImportAbstractParser {
|
||||
public class Swagger2Parser extends SwaggerAbstractParser {
|
||||
|
||||
private Map<String, Model> definitions = null;
|
||||
|
||||
|
@ -47,11 +45,11 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
|
||||
ApiDefinitionImport definitionImport = new ApiDefinitionImport();
|
||||
this.projectId = request.getProjectId();
|
||||
definitionImport.setData(parseRequests(swagger, request.isSaved()));
|
||||
definitionImport.setData(parseRequests(swagger, request));
|
||||
return definitionImport;
|
||||
}
|
||||
|
||||
private List<ApiDefinitionResult> parseRequests(Swagger swagger, boolean isSaved) {
|
||||
private List<ApiDefinitionResult> parseRequests(Swagger swagger, ApiTestImportRequest importRequest) {
|
||||
Map<String, Path> paths = swagger.getPaths();
|
||||
Set<String> pathNames = paths.keySet();
|
||||
|
||||
|
@ -59,6 +57,8 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
|
||||
List<ApiDefinitionResult> results = new ArrayList<>();
|
||||
|
||||
ApiModule parentNode = getSelectModule(importRequest.getModuleId());
|
||||
|
||||
for (String pathName : pathNames) {
|
||||
Path path = paths.get(pathName);
|
||||
Map<HttpMethod, Operation> operationMap = path.getOperationMap();
|
||||
|
@ -70,7 +70,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
parseParameters(operation, request);
|
||||
apiDefinition.setRequest(JSON.toJSONString(request));
|
||||
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses())));
|
||||
buildModule(apiDefinition, operation, isSaved);
|
||||
buildModule(parentNode, apiDefinition, operation.getTags(), importRequest.isSaved());
|
||||
results.add(apiDefinition);
|
||||
}
|
||||
}
|
||||
|
@ -79,20 +79,6 @@ public class Swagger2Parser extends ApiImportAbstractParser {
|
|||
return results;
|
||||
}
|
||||
|
||||
private void buildModule(ApiDefinitionResult apiDefinition, Operation operation, boolean isSaved) {
|
||||
List<String> tags = operation.getTags();
|
||||
if (tags != null) {
|
||||
tags.forEach(tag -> {
|
||||
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
|
||||
ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1);
|
||||
if (isSaved) {
|
||||
createModule(module);
|
||||
}
|
||||
apiDefinition.setModuleId(module.getId());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ApiDefinitionResult buildApiDefinition(String id, Operation operation, String path, String method) {
|
||||
String name = "";
|
||||
if (StringUtils.isNotBlank(operation.getSummary())) {
|
||||
|
|
|
@ -11,10 +11,8 @@ import io.metersphere.api.dto.definition.response.HttpResponse;
|
|||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.request.RequestType;
|
||||
import io.metersphere.api.service.ApiModuleService;
|
||||
import io.metersphere.base.domain.ApiModule;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.XMLUtils;
|
||||
import io.swagger.parser.OpenAPIParser;
|
||||
|
@ -34,7 +32,7 @@ import java.io.InputStream;
|
|||
import java.util.*;
|
||||
|
||||
|
||||
public class Swagger3Parser extends ApiImportAbstractParser {
|
||||
public class Swagger3Parser extends SwaggerAbstractParser {
|
||||
|
||||
private Components components;
|
||||
|
||||
|
@ -67,11 +65,11 @@ public class Swagger3Parser extends ApiImportAbstractParser {
|
|||
|
||||
ApiDefinitionImport definitionImport = new ApiDefinitionImport();
|
||||
this.projectId = request.getProjectId();
|
||||
definitionImport.setData(parseRequests(openAPI, request.isSaved()));
|
||||
definitionImport.setData(parseRequests(openAPI, request));
|
||||
return definitionImport;
|
||||
}
|
||||
|
||||
private List<ApiDefinitionResult> parseRequests(OpenAPI openAPI, boolean isSaved) {
|
||||
private List<ApiDefinitionResult> parseRequests(OpenAPI openAPI, ApiTestImportRequest importRequest) {
|
||||
Paths paths = openAPI.getPaths();
|
||||
|
||||
Set<String> pathNames = paths.keySet();
|
||||
|
@ -80,6 +78,8 @@ public class Swagger3Parser extends ApiImportAbstractParser {
|
|||
|
||||
List<ApiDefinitionResult> results = new ArrayList<>();
|
||||
|
||||
ApiModule parentNode = getSelectModule(importRequest.getModuleId());
|
||||
|
||||
for (String pathName : pathNames) {
|
||||
PathItem pathItem = paths.get(pathName);
|
||||
|
||||
|
@ -102,7 +102,7 @@ public class Swagger3Parser extends ApiImportAbstractParser {
|
|||
parseRequestBody(operation.getRequestBody(), request.getBody());
|
||||
apiDefinition.setRequest(JSON.toJSONString(request));
|
||||
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses())));
|
||||
buildModule(apiDefinition, operation, isSaved);
|
||||
buildModule(parentNode, apiDefinition, operation.getTags(), importRequest.isSaved());
|
||||
results.add(apiDefinition);
|
||||
}
|
||||
}
|
||||
|
@ -111,20 +111,6 @@ public class Swagger3Parser extends ApiImportAbstractParser {
|
|||
return results;
|
||||
}
|
||||
|
||||
private void buildModule(ApiDefinitionResult apiDefinition, Operation operation, boolean isSaved) {
|
||||
List<String> tags = operation.getTags();
|
||||
if (tags != null) {
|
||||
tags.forEach(tag -> {
|
||||
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
|
||||
ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1);
|
||||
if (isSaved) {
|
||||
createModule(module);
|
||||
}
|
||||
apiDefinition.setModuleId(module.getId());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ApiDefinitionResult buildApiDefinition(String id, Operation operation, String path, String method) {
|
||||
String name = "";
|
||||
if (StringUtils.isNotBlank(operation.getSummary())) {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package io.metersphere.api.parse;
|
||||
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionResult;
|
||||
import io.metersphere.base.domain.ApiModule;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SwaggerAbstractParser extends ApiImportAbstractParser {
|
||||
|
||||
protected void buildModule(ApiModule parentModule, ApiDefinitionResult apiDefinition, List<String> tags, boolean isSaved) {
|
||||
if (tags != null) {
|
||||
tags.forEach(tag -> {
|
||||
ApiModule module = buildModule(parentModule, tag, isSaved);
|
||||
apiDefinition.setModuleId(module.getId());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,6 @@ import com.alibaba.fastjson.JSON;
|
|||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.api.dto.ApiTestImportRequest;
|
||||
import io.metersphere.api.dto.automation.ApiScenarioDTO;
|
||||
import io.metersphere.api.dto.automation.ApiScenarioRequest;
|
||||
import io.metersphere.api.dto.automation.ReferenceDTO;
|
||||
import io.metersphere.api.dto.datacount.ApiDataCountResult;
|
||||
|
@ -18,6 +17,7 @@ import io.metersphere.api.parse.ApiImportParserFactory;
|
|||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.ApiDefinitionMapper;
|
||||
import io.metersphere.base.mapper.ApiTestFileMapper;
|
||||
import io.metersphere.base.mapper.ProjectMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiDefinitionMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
|
||||
|
@ -71,6 +71,8 @@ public class ApiDefinitionService {
|
|||
private ExtApiScenarioMapper extApiScenarioMapper;
|
||||
@Resource
|
||||
private ExtTestPlanMapper extTestPlanMapper;
|
||||
@Resource
|
||||
private ProjectMapper projectMapper;
|
||||
|
||||
private static Cache cache = Cache.newHardMemoryCache(0, 3600 * 24);
|
||||
|
||||
|
@ -178,7 +180,8 @@ public class ApiDefinitionService {
|
|||
example.createCriteria().andMethodEqualTo(request.getMethod()).andStatusNotEqualTo("Trash")
|
||||
.andProtocolEqualTo(request.getProtocol()).andPathEqualTo(request.getPath())
|
||||
.andProjectIdEqualTo(request.getProjectId()).andIdNotEqualTo(request.getId());
|
||||
if (apiDefinitionMapper.countByExample(example) > 0) {
|
||||
Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
|
||||
if (apiDefinitionMapper.countByExample(example) > 0 && !project.getRepeatable()) {
|
||||
MSException.throwException(Translator.get("api_definition_url_not_repeating"));
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.metersphere.api.dto.SaveHistoricalDataUpgrade;
|
||||
import io.metersphere.api.dto.automation.ScenarioStatus;
|
||||
import io.metersphere.api.dto.definition.request.MsScenario;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.definition.request.assertions.MsAssertions;
|
||||
import io.metersphere.api.dto.definition.request.controller.MsIfController;
|
||||
import io.metersphere.api.dto.definition.request.extract.MsExtract;
|
||||
import io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor;
|
||||
import io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsDubboSampler;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
|
||||
import io.metersphere.api.dto.definition.request.timer.MsConstantTimer;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.Scenario;
|
||||
import io.metersphere.api.dto.scenario.request.*;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.ApiScenarioMapper;
|
||||
import io.metersphere.base.mapper.ApiTestMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.DateUtils;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.ibatis.session.ExecutorType;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class HistoricalDataUpgradeService {
|
||||
@Resource
|
||||
private ApiTestMapper apiTestMapper;
|
||||
@Resource
|
||||
private ExtApiScenarioMapper extApiScenarioMapper;
|
||||
@Resource
|
||||
SqlSessionFactory sqlSessionFactory;
|
||||
|
||||
private int getNextNum(String projectId) {
|
||||
ApiScenario apiScenario = extApiScenarioMapper.getNextNum(projectId);
|
||||
if (apiScenario == null) {
|
||||
return 100001;
|
||||
} else {
|
||||
return Optional.of(apiScenario.getNum() + 1).orElse(100001);
|
||||
}
|
||||
}
|
||||
|
||||
private MsScenario createScenario(Scenario oldScenario) {
|
||||
MsScenario scenario = new MsScenario();
|
||||
scenario.setVariables(oldScenario.getVariables());
|
||||
scenario.setName(oldScenario.getName());
|
||||
scenario.setEnableCookieShare(oldScenario.isEnableCookieShare());
|
||||
scenario.setEnvironmentId(oldScenario.getEnvironmentId());
|
||||
scenario.setReferenced("Upgrade");
|
||||
scenario.setId(oldScenario.getId());
|
||||
scenario.setResourceId(UUID.randomUUID().toString());
|
||||
LinkedList<MsTestElement> testElements = new LinkedList<>();
|
||||
int index = 1;
|
||||
for (Request request : oldScenario.getRequests()) {
|
||||
// 条件控制器
|
||||
MsIfController ifController = null;
|
||||
if (request.getController() != null && StringUtils.isNotEmpty(request.getController().getValue())
|
||||
&& StringUtils.isNotEmpty(request.getController().getVariable())) {
|
||||
ifController = new MsIfController();
|
||||
BeanUtils.copyBean(ifController, request.getController());
|
||||
ifController.setType("IfController");
|
||||
ifController.setName("IfController");
|
||||
ifController.setIndex(index + "");
|
||||
ifController.setResourceId(UUID.randomUUID().toString());
|
||||
}
|
||||
// 等待控制器
|
||||
if (request.getTimer() != null && StringUtils.isNotEmpty(request.getTimer().getDelay())) {
|
||||
MsConstantTimer constantTimer = new MsConstantTimer();
|
||||
BeanUtils.copyBean(constantTimer, request.getTimer());
|
||||
constantTimer.setType("ConstantTimer");
|
||||
constantTimer.setIndex(index + "");
|
||||
constantTimer.setResourceId(UUID.randomUUID().toString());
|
||||
testElements.add(constantTimer);
|
||||
}
|
||||
|
||||
MsTestElement element = null;
|
||||
if (request instanceof HttpRequest) {
|
||||
element = new MsHTTPSamplerProxy();
|
||||
HttpRequest request1 = (HttpRequest) request;
|
||||
if (StringUtils.isEmpty(request1.getPath()) && StringUtils.isNotEmpty(request1.getUrl())) {
|
||||
try {
|
||||
URL urlObject = new URL(request1.getUrl());
|
||||
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
|
||||
request1.setPath(envPath);
|
||||
} catch (Exception ex) {
|
||||
LogUtil.error(ex.getMessage());
|
||||
}
|
||||
}
|
||||
if (request1.getBody() != null && request1.getBody().isOldKV()) {
|
||||
request1.getBody().setType(Body.FORM_DATA);
|
||||
}
|
||||
BeanUtils.copyBean(element, request1);
|
||||
((MsHTTPSamplerProxy) element).setProtocol(RequestType.HTTP);
|
||||
((MsHTTPSamplerProxy) element).setArguments(request1.getParameters());
|
||||
element.setType("HTTPSamplerProxy");
|
||||
}
|
||||
if (request instanceof DubboRequest) {
|
||||
String requestJson = JSON.toJSONString(request);
|
||||
element = JSON.parseObject(requestJson, MsDubboSampler.class);
|
||||
element.setType("DubboSampler");
|
||||
}
|
||||
if (request instanceof SqlRequest) {
|
||||
element = new MsJDBCSampler();
|
||||
SqlRequest request1 = (SqlRequest) request;
|
||||
BeanUtils.copyBean(element, request1);
|
||||
element.setType("JDBCSampler");
|
||||
}
|
||||
if (request instanceof TCPRequest) {
|
||||
element = new MsTCPSampler();
|
||||
TCPRequest request1 = (TCPRequest) request;
|
||||
BeanUtils.copyBean(element, request1);
|
||||
element.setType("TCPSampler");
|
||||
}
|
||||
element.setIndex(index + "");
|
||||
element.setResourceId(UUID.randomUUID().toString());
|
||||
LinkedList<MsTestElement> msTestElements = new LinkedList<>();
|
||||
// 断言规则
|
||||
if (request.getAssertions() != null && ((request.getAssertions().getDuration() != null && request.getAssertions().getDuration().getValue() > 0) ||
|
||||
CollectionUtils.isNotEmpty(request.getAssertions().getJsonPath()) || CollectionUtils.isNotEmpty(request.getAssertions().getJsr223()) ||
|
||||
CollectionUtils.isNotEmpty(request.getAssertions().getRegex()) || CollectionUtils.isNotEmpty(request.getAssertions().getXpath2()))) {
|
||||
String assertions = JSON.toJSONString(request.getAssertions());
|
||||
MsAssertions msAssertions = JSON.parseObject(assertions, MsAssertions.class);
|
||||
msAssertions.setType("Assertions");
|
||||
msAssertions.setIndex(index + "");
|
||||
msAssertions.setResourceId(UUID.randomUUID().toString());
|
||||
msTestElements.add(msAssertions);
|
||||
}
|
||||
// 提取规则
|
||||
if (request.getExtract() != null && (CollectionUtils.isNotEmpty(request.getExtract().getJson()) ||
|
||||
CollectionUtils.isNotEmpty(request.getExtract().getRegex()) || CollectionUtils.isNotEmpty(request.getExtract().getXpath()))) {
|
||||
String extractJson = JSON.toJSONString(request.getExtract());
|
||||
MsExtract extract = JSON.parseObject(extractJson, MsExtract.class);
|
||||
extract.setType("Extract");
|
||||
extract.setIndex(index + "");
|
||||
extract.setResourceId(UUID.randomUUID().toString());
|
||||
msTestElements.add(extract);
|
||||
}
|
||||
// 前置脚本
|
||||
if (request.getJsr223PreProcessor() != null && StringUtils.isNotEmpty(request.getJsr223PreProcessor().getScript())) {
|
||||
String preJson = JSON.toJSONString(request.getJsr223PreProcessor());
|
||||
MsJSR223PreProcessor preProcessor = JSON.parseObject(preJson, MsJSR223PreProcessor.class);
|
||||
preProcessor.setType("JSR223PreProcessor");
|
||||
preProcessor.setIndex(index + "");
|
||||
preProcessor.setResourceId(UUID.randomUUID().toString());
|
||||
msTestElements.add(preProcessor);
|
||||
}
|
||||
// 后置脚本
|
||||
if (request.getJsr223PostProcessor() != null && StringUtils.isNotEmpty(request.getJsr223PostProcessor().getScript())) {
|
||||
String preJson = JSON.toJSONString(request.getJsr223PostProcessor());
|
||||
MsJSR223PostProcessor preProcessor = JSON.parseObject(preJson, MsJSR223PostProcessor.class);
|
||||
preProcessor.setType("JSR223PostProcessor");
|
||||
preProcessor.setIndex(index + "");
|
||||
preProcessor.setResourceId(UUID.randomUUID().toString());
|
||||
msTestElements.add(preProcessor);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(msTestElements)) {
|
||||
element.setHashTree(msTestElements);
|
||||
}
|
||||
if (ifController != null) {
|
||||
LinkedList<MsTestElement> elements = new LinkedList<>();
|
||||
elements.add(element);
|
||||
ifController.setHashTree(elements);
|
||||
testElements.add(ifController);
|
||||
} else {
|
||||
testElements.add(element);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
scenario.setHashTree(testElements);
|
||||
return scenario;
|
||||
}
|
||||
|
||||
private ApiScenarioWithBLOBs checkNameExist(Scenario oldScenario, String projectId, ApiScenarioMapper mapper) {
|
||||
ApiScenarioExample example = new ApiScenarioExample();
|
||||
example.createCriteria().andIdEqualTo(oldScenario.getId());
|
||||
List<ApiScenarioWithBLOBs> list = mapper.selectByExampleWithBLOBs(example);
|
||||
if (list.size() > 0) {
|
||||
return list.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
|
||||
|
||||
//文件的拷贝
|
||||
private static void copyFile(String sourcePath, String newPath) {
|
||||
File readfile = new File(sourcePath);
|
||||
File newFile = new File(newPath);
|
||||
BufferedWriter bufferedWriter = null;
|
||||
Writer writer = null;
|
||||
FileOutputStream fileOutputStream = null;
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
fileOutputStream = new FileOutputStream(newFile, true);
|
||||
writer = new OutputStreamWriter(fileOutputStream, "UTF-8");
|
||||
bufferedWriter = new BufferedWriter(writer);
|
||||
|
||||
bufferedReader = new BufferedReader(new FileReader(readfile));
|
||||
|
||||
String line = null;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
bufferedWriter.write(line);
|
||||
bufferedWriter.newLine();
|
||||
bufferedWriter.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (bufferedWriter != null) {
|
||||
bufferedWriter.close();
|
||||
}
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyDir(String sourcePathDir, String newPathDir) {
|
||||
File start = new File(sourcePathDir);
|
||||
File end = new File(newPathDir);
|
||||
String[] filePath = start.list();
|
||||
if (!end.exists()) {
|
||||
end.mkdir();
|
||||
}
|
||||
for (String temp : filePath) {
|
||||
//添加满足情况的条件
|
||||
if (new File(sourcePathDir + File.separator + temp).isFile()) {
|
||||
//为文件则进行拷贝
|
||||
copyFile(sourcePathDir + File.separator + temp, newPathDir + File.separator + temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createBodyFiles(String testId) {
|
||||
String dir = BODY_FILE_DIR + "/" + testId;
|
||||
File testDir = new File(dir);
|
||||
if (testDir.exists()) {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
copyDir(dir, BODY_FILE_DIR);
|
||||
}
|
||||
|
||||
private void createApiScenarioWithBLOBs(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade, Scenario oldScenario, String scenarioDefinition, ApiScenarioMapper mapper) {
|
||||
if (StringUtils.isEmpty(oldScenario.getName())) {
|
||||
oldScenario.setName("默认名称-" + DateUtils.getTimeStr(System.currentTimeMillis()));
|
||||
}
|
||||
ApiScenarioWithBLOBs scenario = checkNameExist(oldScenario, saveHistoricalDataUpgrade.getProjectId(), mapper);
|
||||
if (scenario != null) {
|
||||
scenario.setName(oldScenario.getName());
|
||||
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.setScenarioDefinition(scenarioDefinition);
|
||||
scenario.setUpdateTime(System.currentTimeMillis());
|
||||
scenario.setStatus(ScenarioStatus.Underway.name());
|
||||
scenario.setUserId(SessionUtils.getUserId());
|
||||
scenario.setNum(getNextNum(saveHistoricalDataUpgrade.getProjectId()));
|
||||
mapper.updateByPrimaryKeySelective(scenario);
|
||||
} else {
|
||||
scenario = new ApiScenarioWithBLOBs();
|
||||
scenario.setId(oldScenario.getId());
|
||||
scenario.setName(oldScenario.getName());
|
||||
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.setScenarioDefinition(scenarioDefinition);
|
||||
scenario.setCreateTime(System.currentTimeMillis());
|
||||
scenario.setUpdateTime(System.currentTimeMillis());
|
||||
scenario.setStatus(ScenarioStatus.Underway.name());
|
||||
scenario.setUserId(SessionUtils.getUserId());
|
||||
scenario.setNum(getNextNum(saveHistoricalDataUpgrade.getProjectId()));
|
||||
mapper.insert(scenario);
|
||||
}
|
||||
}
|
||||
|
||||
public String upgrade(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade) {
|
||||
ApiTestExample example = new ApiTestExample();
|
||||
example.createCriteria().andIdIn(saveHistoricalDataUpgrade.getTestIds());
|
||||
List<ApiTest> blobs = apiTestMapper.selectByExampleWithBLOBs(example);
|
||||
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
|
||||
ApiScenarioMapper mapper = sqlSession.getMapper(ApiScenarioMapper.class);
|
||||
for (ApiTest test : blobs) {
|
||||
// 附件迁移
|
||||
createBodyFiles(test.getId());
|
||||
|
||||
List<Scenario> scenarios = JSON.parseArray(test.getScenarioDefinition(), Scenario.class);
|
||||
if (CollectionUtils.isNotEmpty(scenarios)) {
|
||||
// 批量处理
|
||||
for (Scenario scenario : scenarios) {
|
||||
MsScenario scenario1 = createScenario(scenario);
|
||||
String scenarioDefinition = JSON.toJSONString(scenario1);
|
||||
createApiScenarioWithBLOBs(saveHistoricalDataUpgrade, scenario, scenarioDefinition, mapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlSession.flushStatements();
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
package io.metersphere.base.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class Project implements Serializable {
|
||||
private String id;
|
||||
|
@ -23,5 +24,7 @@ public class Project implements Serializable {
|
|||
|
||||
private String zentaoId;
|
||||
|
||||
private Boolean repeatable;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -713,6 +713,66 @@ public class ProjectExample {
|
|||
addCriterion("zentao_id not between", value1, value2, "zentaoId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableIsNull() {
|
||||
addCriterion("`repeatable` is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableIsNotNull() {
|
||||
addCriterion("`repeatable` is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableEqualTo(Boolean value) {
|
||||
addCriterion("`repeatable` =", value, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableNotEqualTo(Boolean value) {
|
||||
addCriterion("`repeatable` <>", value, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableGreaterThan(Boolean value) {
|
||||
addCriterion("`repeatable` >", value, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableGreaterThanOrEqualTo(Boolean value) {
|
||||
addCriterion("`repeatable` >=", value, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableLessThan(Boolean value) {
|
||||
addCriterion("`repeatable` <", value, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableLessThanOrEqualTo(Boolean value) {
|
||||
addCriterion("`repeatable` <=", value, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableIn(List<Boolean> values) {
|
||||
addCriterion("`repeatable` in", values, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableNotIn(List<Boolean> values) {
|
||||
addCriterion("`repeatable` not in", values, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableBetween(Boolean value1, Boolean value2) {
|
||||
addCriterion("`repeatable` between", value1, value2, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andRepeatableNotBetween(Boolean value1, Boolean value2) {
|
||||
addCriterion("`repeatable` not between", value1, value2, "repeatable");
|
||||
return (Criteria) this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Criteria extends GeneratedCriteria {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<result column="tapd_id" jdbcType="VARCHAR" property="tapdId" />
|
||||
<result column="jira_key" jdbcType="VARCHAR" property="jiraKey" />
|
||||
<result column="zentao_id" jdbcType="VARCHAR" property="zentaoId" />
|
||||
<result column="repeatable" jdbcType="BIT" property="repeatable" />
|
||||
</resultMap>
|
||||
<sql id="Example_Where_Clause">
|
||||
<where>
|
||||
|
@ -72,7 +73,7 @@
|
|||
</sql>
|
||||
<sql id="Base_Column_List">
|
||||
id, workspace_id, `name`, description, create_time, update_time, tapd_id, jira_key,
|
||||
zentao_id
|
||||
zentao_id, `repeatable`
|
||||
</sql>
|
||||
<select id="selectByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultMap="BaseResultMap">
|
||||
select
|
||||
|
@ -107,12 +108,12 @@
|
|||
<insert id="insert" parameterType="io.metersphere.base.domain.Project">
|
||||
insert into project (id, workspace_id, `name`,
|
||||
description, create_time, update_time,
|
||||
tapd_id, jira_key, zentao_id
|
||||
)
|
||||
tapd_id, jira_key, zentao_id,
|
||||
`repeatable`)
|
||||
values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
|
||||
#{description,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
|
||||
#{tapdId,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR}, #{zentaoId,jdbcType=VARCHAR}
|
||||
)
|
||||
#{tapdId,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR}, #{zentaoId,jdbcType=VARCHAR},
|
||||
#{repeatable,jdbcType=BIT})
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="io.metersphere.base.domain.Project">
|
||||
insert into project
|
||||
|
@ -144,6 +145,9 @@
|
|||
<if test="zentaoId != null">
|
||||
zentao_id,
|
||||
</if>
|
||||
<if test="repeatable != null">
|
||||
`repeatable`,
|
||||
</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
|
@ -173,6 +177,9 @@
|
|||
<if test="zentaoId != null">
|
||||
#{zentaoId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="repeatable != null">
|
||||
#{repeatable,jdbcType=BIT},
|
||||
</if>
|
||||
</trim>
|
||||
</insert>
|
||||
<select id="countByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultType="java.lang.Long">
|
||||
|
@ -211,6 +218,9 @@
|
|||
<if test="record.zentaoId != null">
|
||||
zentao_id = #{record.zentaoId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.repeatable != null">
|
||||
`repeatable` = #{record.repeatable,jdbcType=BIT},
|
||||
</if>
|
||||
</set>
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
|
@ -226,7 +236,8 @@
|
|||
update_time = #{record.updateTime,jdbcType=BIGINT},
|
||||
tapd_id = #{record.tapdId,jdbcType=VARCHAR},
|
||||
jira_key = #{record.jiraKey,jdbcType=VARCHAR},
|
||||
zentao_id = #{record.zentaoId,jdbcType=VARCHAR}
|
||||
zentao_id = #{record.zentaoId,jdbcType=VARCHAR},
|
||||
`repeatable` = #{record.repeatable,jdbcType=BIT}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
</if>
|
||||
|
@ -258,6 +269,9 @@
|
|||
<if test="zentaoId != null">
|
||||
zentao_id = #{zentaoId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="repeatable != null">
|
||||
`repeatable` = #{repeatable,jdbcType=BIT},
|
||||
</if>
|
||||
</set>
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
|
@ -270,7 +284,8 @@
|
|||
update_time = #{updateTime,jdbcType=BIGINT},
|
||||
tapd_id = #{tapdId,jdbcType=VARCHAR},
|
||||
jira_key = #{jiraKey,jdbcType=VARCHAR},
|
||||
zentao_id = #{zentaoId,jdbcType=VARCHAR}
|
||||
zentao_id = #{zentaoId,jdbcType=VARCHAR},
|
||||
`repeatable` = #{repeatable,jdbcType=BIT}
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
</mapper>
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<select id="getProjectWithWorkspace" resultType="io.metersphere.dto.ProjectDTO">
|
||||
select p.id, p.workspace_id, p.name, p.description, p.update_time,
|
||||
p.create_time, w.id as workspaceId, w.name as workspaceName, p.tapd_id, p.jira_key, p.zentao_id
|
||||
p.create_time, w.id as workspaceId, w.name as workspaceName, p.tapd_id, p.jira_key, p.zentao_id,p.repeatable
|
||||
from project p
|
||||
join workspace w on p.workspace_id = w.id
|
||||
<where>
|
||||
|
|
|
@ -17,4 +17,6 @@ public class ProjectDTO {
|
|||
private String tapdId;
|
||||
private String jiraKey;
|
||||
private String zentaoId;
|
||||
private boolean repeatable;
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import javax.validation.constraints.Pattern;
|
|||
public class TestCaseExcelDataCn extends TestCaseExcelData {
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 50)
|
||||
@Length(max = 255)
|
||||
@ExcelProperty("用例名称")
|
||||
private String name;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import javax.validation.constraints.Pattern;
|
|||
public class TestCaseExcelDataTw extends TestCaseExcelData {
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 50)
|
||||
@Length(max = 255)
|
||||
@ExcelProperty("用例名稱")
|
||||
private String name;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import javax.validation.constraints.Pattern;
|
|||
public class TestCaseExcelDataUs extends TestCaseExcelData {
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 50)
|
||||
@Length(max = 255)
|
||||
@ExcelProperty("Name")
|
||||
private String name;
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 068127ce59ea8b016434ed52a9de4a7a4b13bdb4
|
||||
Subproject commit 9f4a9bbf46fc1333dbcccea21f83e27e3ec10b1f
|
|
@ -1,5 +1,5 @@
|
|||
alter table test_plan add project_id varchar(50) null comment '测试计划所属项目';
|
||||
ALTER TABLE api_test_case MODIFY COLUMN name varchar(255) NOT NULL COMMENT 'Test name';
|
||||
ALTER TABLE api_test_case MODIFY COLUMN name varchar(255) NOT NULL COMMENT 'Test case name';
|
||||
|
||||
DROP PROCEDURE IF EXISTS test_cursor;
|
||||
DELIMITER //
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE project ADD repeatable tinyint(1) DEFAULT null;
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE test_case MODIFY COLUMN name varchar(255) NOT NULL COMMENT 'Test case name';
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
<!--要生成的数据库表 -->
|
||||
|
||||
<table tableName="api_scenario"/>
|
||||
<table tableName="project"/>
|
||||
<!--<table tableName="test_plan_api_scenario"/>-->
|
||||
<!--<table tableName="test_plan"/>-->
|
||||
<!--<table tableName="api_scenario_report"/>-->
|
||||
|
|
|
@ -87,7 +87,7 @@ export default {
|
|||
isHide: true,
|
||||
activeName: 'default',
|
||||
currentModule: null,
|
||||
moduleOptions: {},
|
||||
moduleOptions: [],
|
||||
tabs: [],
|
||||
trashEnable: false,
|
||||
selectNodeIds: [],
|
||||
|
@ -114,7 +114,12 @@ export default {
|
|||
this.$post(url, {id:scenarioId,projectId:projectId}, response => {
|
||||
let data = response.data;
|
||||
if(data!=null){
|
||||
//如果树未加载
|
||||
if(JSON.stringify(this.moduleOptions) === '{}' ){
|
||||
this.$refs.nodeTree.list();
|
||||
}
|
||||
let row = data.listObject[0];
|
||||
row.tags = JSON.parse(row.tags);
|
||||
this.editScenario(row);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -58,8 +58,8 @@
|
|||
<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-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'"/>
|
||||
<ms-dubbo-basis-parameters :request="request" v-if="request.protocol==='DUBBO' || request.protocol==='dubbo://'|| request.type==='DubboSampler'"/>
|
||||
<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>
|
||||
<ms-request-result-tail :currentProtocol="request.protocol" :response="request.requestResult" ref="runResult"/>
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
|
||||
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
|
||||
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
</el-tabs>
|
||||
</el-form>
|
||||
</div>
|
||||
<div v-if="showScript">
|
||||
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 20px;width: 100%">
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"
|
||||
|
@ -74,7 +75,7 @@
|
|||
<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="3" class="ms-left-cell" v-if="showScript">
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div v-if="showScript">
|
||||
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 20px;width: 100%">
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"
|
||||
|
@ -53,7 +53,7 @@
|
|||
<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
:title="$t('commons.test')"
|
||||
@create="create" :createTip="$t('load_test.create')" :runTip="$t('load_test.run')"
|
||||
:show-run="true"
|
||||
@runTest="runTest"/>
|
||||
@runTest="runTest" @historicalDataUpgrade="historicalDataUpgrade"/>
|
||||
|
||||
</template>
|
||||
|
||||
|
@ -54,7 +54,8 @@
|
|||
</el-card>
|
||||
|
||||
<api-copy-dialog ref="apiCopy" @refresh="search"/>
|
||||
|
||||
<ms-upgrade ref="upgrade" :select-ids="selectIds"
|
||||
:select-project-names="selectProjectNames" :select-project-id="selectProjectId"/>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
</template>
|
||||
|
@ -72,13 +73,14 @@
|
|||
import {TEST_CONFIGS} from "../../common/components/search/search-components";
|
||||
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
|
||||
import ApiCopyDialog from "./components/ApiCopyDialog";
|
||||
import MsUpgrade from "./Upgrade";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ApiCopyDialog,
|
||||
OneClickOperation,
|
||||
MsTableOperators,
|
||||
MsApiTestStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination, MsTableOperator
|
||||
MsApiTestStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination, MsTableOperator, MsUpgrade
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -204,6 +206,13 @@
|
|||
_filter(filters, this.condition);
|
||||
this.init();
|
||||
},
|
||||
historicalDataUpgrade() {
|
||||
if (this.selectIds.size < 1) {
|
||||
this.$warning(this.$t('test_track.plan_view.select_manipulate'));
|
||||
} else {
|
||||
this.$refs.upgrade.openOneClickOperation();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
title="选则模块"
|
||||
:visible.sync="oneClickOperationVisible"
|
||||
width="600px"
|
||||
left
|
||||
:destroy-on-close="true"
|
||||
show-close
|
||||
@closed="handleClose" v-loading="loading">
|
||||
<el-form :model="ruleForm" label-position="right" label-width="80px" size="small" :rules="rule">
|
||||
<el-form-item :label="$t('test_track.module.module')" prop="apiScenarioModuleId">
|
||||
<el-select size="small" style="width: 80%" v-model="apiScenarioModuleId">
|
||||
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<ms-dialog-footer
|
||||
@cancel="oneClickOperationVisible = false"
|
||||
@confirm="confirm"/>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsDialogFooter from '../../common/components/MsDialogFooter'
|
||||
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
|
||||
import MsApiReportStatus from "../report/ApiReportStatus";
|
||||
import MsApiReportDialog from "./ApiReportDialog";
|
||||
import {getUUID, getCurrentProjectID} from "@/common/js/utils";
|
||||
import {buildNodePath} from "../definition/model/NodeTree";
|
||||
|
||||
|
||||
export default {
|
||||
name: "MsUpgrade",
|
||||
components: {
|
||||
MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig, MsDialogFooter
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
oneClickOperationVisible: false,
|
||||
apiScenarioModuleId: "",
|
||||
moduleOptions: [],
|
||||
ruleForm: {},
|
||||
loading: false,
|
||||
rule: {
|
||||
apiScenarioModuleId: [
|
||||
{required: true, message: this.$t('test_track.module.module'), trigger: 'blur'},
|
||||
],
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initModule();
|
||||
},
|
||||
props: {
|
||||
selectIds: {
|
||||
type: Set
|
||||
},
|
||||
selectProjectNames: {
|
||||
type: Set
|
||||
},
|
||||
selectProjectId: {
|
||||
type: Set
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openOneClickOperation() {
|
||||
this.oneClickOperationVisible = true;
|
||||
},
|
||||
getPath(id) {
|
||||
let path = this.moduleOptions.filter(function (item) {
|
||||
return item.id === id ? item.path : "";
|
||||
});
|
||||
return path[0].path;
|
||||
},
|
||||
confirm() {
|
||||
this.loading = true;
|
||||
let arr = Array.from(this.selectIds);
|
||||
let obj = {testIds: arr, projectId: getCurrentProjectID(), modulePath: this.getPath(this.apiScenarioModuleId), moduleId: this.apiScenarioModuleId};
|
||||
this.$post("/api/historicalDataUpgrade", obj, response => {
|
||||
this.loading = false;
|
||||
this.$success(this.$t('organization.integration.successful_operation'));
|
||||
this.oneClickOperationVisible = false;
|
||||
})
|
||||
},
|
||||
initModule() {
|
||||
let url = "/api/automation/module/list/" + getCurrentProjectID();
|
||||
this.$get(url, response => {
|
||||
if (response.data != undefined && response.data != null) {
|
||||
this.data = response.data;
|
||||
let modules = [];
|
||||
this.data.forEach(node => {
|
||||
buildNodePath(node, {path: ''}, modules);
|
||||
});
|
||||
this.moduleOptions = modules;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleClose() {
|
||||
this.ruleForm = {}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -13,6 +13,8 @@
|
|||
<ms-table-button :is-tester-permission="isTesterPermission" v-if="showRun" icon="el-icon-video-play"
|
||||
type="primary"
|
||||
:content="runTip" @click="runTest"/>
|
||||
<ms-table-button :is-tester-permission="isTesterPermission" v-if="showRun" icon="el-icon-circle-plus-outline"
|
||||
content="转场景测试" @click="historicalDataUpgrade"/>
|
||||
|
||||
<slot name="button"></slot>
|
||||
</span>
|
||||
|
@ -84,6 +86,9 @@
|
|||
},
|
||||
runTest() {
|
||||
this.$emit('runTest')
|
||||
},
|
||||
historicalDataUpgrade() {
|
||||
this.$emit('historicalDataUpgrade');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
<el-card class="table-card">
|
||||
<template v-slot:header>
|
||||
<ms-table-header :is-tester-permission="true" :condition.sync="condition" @search="search" @create="create"
|
||||
:create-tip="btnTips" :title="$t('commons.project')"/>
|
||||
:create-tip="btnTips" :title="$t('commons.project')">
|
||||
<template v-slot:button>
|
||||
<ms-table-button :is-tester-permission="true" icon="el-icon-box"
|
||||
:content="$t('api_test.jar_config.title')" @click="openJarConfig"/>
|
||||
</template>
|
||||
</ms-table-header>
|
||||
</template>
|
||||
<el-table border class="adjust-table" :data="items" style="width: 100%" @sort-change="sort">
|
||||
<el-table-column prop="name" :label="$t('commons.name')" width="250" show-overflow-tooltip/>
|
||||
|
@ -48,7 +53,7 @@
|
|||
</el-card>
|
||||
|
||||
<el-dialog :close-on-click-modal="false" :title="title" :visible.sync="createVisible" destroy-on-close @close="handleClose">
|
||||
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
|
||||
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="140px" size="small">
|
||||
<el-form-item :label="$t('commons.name')" prop="name">
|
||||
<el-input v-model="form.name" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
|
@ -64,6 +69,9 @@
|
|||
<el-form-item :label="$t('project.zentao_id')" v-if="zentao">
|
||||
<el-input v-model="form.zentaoId" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('project.repeatable')" prop="repeatable">
|
||||
<el-switch v-model="form.repeatable"></el-switch>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template v-slot:footer>
|
||||
<div class="dialog-footer">
|
||||
|
@ -78,6 +86,8 @@
|
|||
|
||||
<api-environment-config ref="environmentConfig"/>
|
||||
|
||||
<ms-jar-config ref="jarConfig"/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -96,10 +106,13 @@ import MsTableOperatorButton from "../../common/components/MsTableOperatorButton
|
|||
import ApiEnvironmentConfig from "../../api/test/components/ApiEnvironmentConfig";
|
||||
import TemplateComponent from "../../track/plan/view/comonents/report/TemplateComponent/TemplateComponent";
|
||||
import {PROJECT_ID} from "@/common/js/constants";
|
||||
|
||||
import MsJarConfig from "../../api/test/components/jar/JarConfig";
|
||||
import MsTableButton from "../../common/components/MsTableButton";
|
||||
export default {
|
||||
name: "MsProject",
|
||||
components: {
|
||||
MsTableButton,
|
||||
MsJarConfig,
|
||||
TemplateComponent,
|
||||
ApiEnvironmentConfig,
|
||||
MsTableOperatorButton,
|
||||
|
@ -188,7 +201,6 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
submit(formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
|
@ -206,6 +218,9 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
openJarConfig() {
|
||||
this.$refs.jarConfig.open();
|
||||
},
|
||||
handleDelete(project) {
|
||||
this.$refs.deleteConfirm.open(project);
|
||||
},
|
||||
|
@ -275,5 +290,4 @@ export default {
|
|||
margin: 0 0;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 7d43154a7c19732407a8e9ace8a7d1ea13c91f36
|
||||
Subproject commit 010ad7a5f072a5e9d368c756a2473bbd20781433
|
|
@ -308,7 +308,8 @@ export default {
|
|||
zentao_id: 'Zentao Project ID',
|
||||
manager: 'Manager',
|
||||
no_data: 'No Data',
|
||||
select: 'Select'
|
||||
select: 'Select',
|
||||
repeatable: 'Interface definition URL repeatable'
|
||||
},
|
||||
member: {
|
||||
create: 'Create',
|
||||
|
|
|
@ -305,7 +305,8 @@ export default {
|
|||
zentao_id: 'Zentao项目ID',
|
||||
manager: '项目管理',
|
||||
no_data: '无数据',
|
||||
select: '选择项目'
|
||||
select: '选择项目',
|
||||
repeatable: '接口定义URL可重复'
|
||||
},
|
||||
member: {
|
||||
create: '添加成员',
|
||||
|
|
|
@ -305,7 +305,8 @@ export default {
|
|||
zentao_id: 'Zentao項目ID',
|
||||
manager: '項目管理',
|
||||
no_data: '無數據',
|
||||
select: '選擇項目'
|
||||
select: '選擇項目',
|
||||
repeatable: '接口定义URL可重复'
|
||||
},
|
||||
member: {
|
||||
create: '添加成員',
|
||||
|
|
Loading…
Reference in New Issue