提交 cbc9c421 编写于 作者: F fit2-zhao

feat(接口定义): TCP 协议基础完成

上级 84e99e1b
......@@ -3,6 +3,7 @@ package io.metersphere.api.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.ApiDefinitionRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
......@@ -81,4 +82,11 @@ public class ApiDefinitionController {
return apiDefinitionService.getDbResult(testId);
}
@PostMapping(value = "/import", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public String testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
return apiDefinitionService.apiTestImport(file, request);
}
}
......@@ -7,6 +7,8 @@ import lombok.Setter;
@Getter
public class ApiTestImportRequest {
private String name;
private String moduleId;
private String modulePath;
private String environmentId;
private String projectId;
private String platform;
......
package io.metersphere.api.dto.definition.parse;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import lombok.Data;
import java.util.List;
@Data
public class ApiDefinitionImport {
private String projectName;
private String protocol;
private List<ApiDefinitionResult> data;
}
package io.metersphere.api.dto.definition.parse.postman;
import lombok.Data;
import java.util.List;
@Data
public class PostmanCollection {
private PostmanCollectionInfo info;
private List<PostmanItem> item;
private List<PostmanKeyValue> variable;
}
package io.metersphere.api.dto.definition.parse.postman;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
@Data
public class PostmanCollectionInfo {
@JSONField(name = "_postman_id")
private String postmanId;
private String name;
private String schema;
}
package io.metersphere.api.dto.definition.parse.postman;
import lombok.Data;
import java.util.List;
@Data
public class PostmanItem {
private String name;
private PostmanRequest request;
private List<PostmanItem> item;
}
package io.metersphere.api.dto.definition.parse.postman;
import lombok.Data;
@Data
public class PostmanKeyValue {
private String key;
private String value;
private String type;
public PostmanKeyValue() {
}
public PostmanKeyValue(String key, String value) {
this.key = key;
this.value = value;
}
}
package io.metersphere.api.dto.definition.parse.postman;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.List;
@Data
public class PostmanRequest {
private String method;
private String schema;
private List<PostmanKeyValue> header;
private JSONObject body;
private JSONObject auth;
private PostmanUrl url;
private String description;
}
package io.metersphere.api.dto.definition.parse.postman;
import lombok.Data;
import java.util.List;
@Data
public class PostmanUrl {
private String raw;
private String protocol;
private String port;
private List<PostmanKeyValue> query;
}
package io.metersphere.api.dto.definition.parse.swagger;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.List;
@Data
public class SwaggerApi {
private String swagger;
private SwaggerInfo info;
private String host;
private String basePath;
private List<String> schemes;
private List<SwaggerTag> tags;
private JSONObject paths;
private JSONObject definitions;
}
package io.metersphere.api.dto.definition.parse.swagger;
import lombok.Data;
@Data
public class SwaggerInfo {
private String version;
private String title;
private String description;
private String termsOfService;
}
package io.metersphere.api.dto.definition.parse.swagger;
import lombok.Data;
@Data
public class SwaggerParameter {
private String name;
private String in;
private String description;
private Boolean required;
private String type;
private String format;
}
package io.metersphere.api.dto.definition.parse.swagger;
import lombok.Data;
import java.util.List;
@Data
public class SwaggerRequest {
private List<String> tags;
private String summary;
private String description;
private String operationId;
private List<String> consumes;
private List<String> produces;
private List<SwaggerParameter> parameters;
}
package io.metersphere.api.dto.definition.parse.swagger;
import lombok.Data;
@Data
public class SwaggerTag {
private String name;
private String description;
}
package io.metersphere.api.parse;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.parse.ApiImport;
import java.io.InputStream;
public interface ApiImportParser {
ApiImport parse(InputStream source, ApiTestImportRequest request);
ApiDefinitionImport parseApi(InputStream source, ApiTestImportRequest request);
}
......@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.commons.constants.MsRequestBodyType;
......@@ -22,6 +23,13 @@ public class MsParser extends ApiImportAbstractParser {
return apiImport;
}
@Override
public ApiDefinitionImport parseApi(InputStream source, ApiTestImportRequest request) {
String testStr = getApiTestStr(source);
ApiDefinitionImport apiImport = JSON.parseObject(testStr, ApiDefinitionImport.class);
return apiImport;
}
private String parsePluginFormat(String testStr) {
JSONObject testObject = JSONObject.parseObject(testStr, Feature.OrderedField);
if (testObject.get("scenarios") != null) {
......
......@@ -3,6 +3,11 @@ package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.parse.postman.*;
import io.metersphere.api.dto.scenario.Body;
......@@ -10,12 +15,17 @@ import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.api.dto.scenario.request.HttpRequest;
import io.metersphere.api.dto.scenario.request.Request;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.commons.constants.MsRequestBodyType;
import io.metersphere.commons.constants.PostmanRequestBodyMode;
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
import java.io.InputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public class PostmanParser extends ApiImportAbstractParser {
......@@ -38,6 +48,67 @@ public class PostmanParser extends ApiImportAbstractParser {
return apiImport;
}
@Override
public ApiDefinitionImport parseApi(InputStream source, ApiTestImportRequest request) {
String testStr = getApiTestStr(source);
PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class);
List<PostmanKeyValue> variables = postmanCollection.getVariable();
ApiDefinitionImport apiImport = new ApiDefinitionImport();
List<ApiDefinitionResult> requests = new ArrayList<>();
parseItem(postmanCollection.getItem(), variables, requests);
apiImport.setData(requests);
return apiImport;
}
private void parseItem(List<PostmanItem> items, List<PostmanKeyValue> variables, List<ApiDefinitionResult> scenarios) {
for (PostmanItem item : items) {
List<PostmanItem> childItems = item.getItem();
if (childItems != null) {
parseItem(childItems, variables, scenarios);
} else {
ApiDefinitionResult request = parsePostman(item);
if (request != null) {
scenarios.add(request);
}
}
}
}
private ApiDefinitionResult parsePostman(PostmanItem requestItem) {
PostmanRequest requestDesc = requestItem.getRequest();
if (requestDesc == null) {
return null;
}
PostmanUrl url = requestDesc.getUrl();
ApiDefinitionResult request = new ApiDefinitionResult();
request.setName(requestItem.getName());
request.setPath(url.getRaw());
request.setMethod(requestDesc.getMethod());
request.setProtocol(RequestType.HTTP);
MsHTTPSamplerProxy requestElement = new MsHTTPSamplerProxy();
requestElement.setName(requestItem.getName() + "Postman MHTTPSamplerProxy");
requestElement.setBody(parseBody(requestDesc));
requestElement.setArguments(parseKeyValue(url.getQuery()));
requestElement.setProtocol(RequestType.HTTP);
requestElement.setPath(url.getRaw());
requestElement.setMethod(requestDesc.getMethod());
requestElement.setId(UUID.randomUUID().toString());
requestElement.setRest(new ArrayList<KeyValue>());
MsHeaderManager headerManager = new MsHeaderManager();
headerManager.setId(UUID.randomUUID().toString());
headerManager.setName(requestItem.getName() + "Postman MsHeaderManager");
headerManager.setHeaders(parseKeyValue(requestDesc.getHeader()));
HashTree tree = new HashTree();
tree.add(headerManager);
LinkedList<MsTestElement> list = new LinkedList<>();
list.add(headerManager);
requestElement.setHashTree(list);
request.setRequest(JSON.toJSONString(requestElement));
return request;
}
private List<KeyValue> parseKeyValue(List<PostmanKeyValue> postmanKeyValues) {
if (postmanKeyValues == null) {
return null;
......
package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
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.Scenario;
import io.metersphere.api.dto.scenario.request.HttpRequest;
import io.metersphere.api.dto.scenario.request.Request;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.commons.constants.MsRequestBodyType;
import io.metersphere.commons.constants.SwaggerParameterType;
import io.swagger.models.*;
......@@ -19,6 +26,7 @@ import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.parser.SwaggerParser;
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
import java.io.InputStream;
import java.util.*;
......@@ -39,6 +47,52 @@ public class Swagger2Parser extends ApiImportAbstractParser {
return apiImport;
}
@Override
public ApiDefinitionImport parseApi(InputStream source, ApiTestImportRequest request) {
ApiImport apiImport = this.parse(source, request);
ApiDefinitionImport definitionImport = new ApiDefinitionImport();
definitionImport.setData(parseSwagger(apiImport));
return definitionImport;
}
private List<ApiDefinitionResult> parseSwagger(ApiImport apiImport) {
List<ApiDefinitionResult> results = new LinkedList<>();
apiImport.getScenarios().forEach(item -> {
item.getRequests().forEach(childItem -> {
if (childItem instanceof HttpRequest) {
HttpRequest res = (HttpRequest) childItem;
ApiDefinitionResult request = new ApiDefinitionResult();
request.setName(res.getName());
request.setPath(res.getPath());
request.setMethod(res.getMethod());
request.setProtocol(RequestType.HTTP);
MsHTTPSamplerProxy requestElement = new MsHTTPSamplerProxy();
requestElement.setName(res.getName() + "Postman MHTTPSamplerProxy");
requestElement.setBody(res.getBody());
requestElement.setArguments(res.getParameters());
requestElement.setProtocol(RequestType.HTTP);
requestElement.setPath(res.getPath());
requestElement.setMethod(res.getMethod());
requestElement.setId(UUID.randomUUID().toString());
requestElement.setRest(new ArrayList<KeyValue>());
MsHeaderManager headerManager = new MsHeaderManager();
headerManager.setId(UUID.randomUUID().toString());
headerManager.setName(res.getName() + "Postman MsHeaderManager");
headerManager.setHeaders(res.getHeaders());
HashTree tree = new HashTree();
tree.add(headerManager);
LinkedList<MsTestElement> list = new LinkedList<>();
list.add(headerManager);
requestElement.setHashTree(list);
request.setRequest(JSON.toJSONString(requestElement));
results.add(request);
}
});
});
return results;
}
private List<Scenario> parseRequests(Swagger swagger) {
Map<String, Path> paths = swagger.getPaths();
Set<String> pathNames = paths.keySet();
......@@ -141,7 +195,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
Model model = definitions.get(simpleRef);
HashSet<String> refSet = new HashSet<>();
refSet.add(simpleRef);
if (model != null ) {
if (model != null) {
JSONObject bodyParameters = getBodyJSONObjectParameters(model.getProperties(), definitions, refSet);
body.setRaw(bodyParameters.toJSONString());
}
......
......@@ -3,10 +3,14 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.TestResult;
import io.metersphere.api.parse.ApiImportParser;
import io.metersphere.api.parse.ApiImportParserFactory;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ApiDefinitionMapper;
......@@ -29,10 +33,7 @@ import sun.security.util.Cache;
import javax.annotation.Resource;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
......@@ -206,6 +207,39 @@ public class ApiDefinitionService {
return test;
}
private ApiDefinition createTest(ApiDefinitionResult request) {
SaveApiDefinitionRequest saveReq = new SaveApiDefinitionRequest();
saveReq.setId(UUID.randomUUID().toString());
saveReq.setName(request.getName());
saveReq.setProtocol(request.getProtocol());
saveReq.setProjectId(request.getProjectId());
saveReq.setPath(request.getPath());
checkNameExist(saveReq);
final ApiDefinition test = new ApiDefinition();
test.setId(request.getId());
test.setName(request.getName());
test.setProtocol(request.getProtocol());
test.setMethod(request.getMethod());
test.setPath(request.getPath());
test.setModuleId(request.getModuleId());
test.setProjectId(request.getProjectId());
test.setRequest(request.getRequest());
test.setCreateTime(System.currentTimeMillis());
test.setUpdateTime(System.currentTimeMillis());
test.setStatus(APITestStatus.Underway.name());
test.setModulePath(request.getModulePath());
test.setResponse(request.getResponse());
test.setEnvironmentId(request.getEnvironmentId());
if (request.getUserId() == null) {
test.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());
} else {
test.setUserId(request.getUserId());
}
test.setDescription(request.getDescription());
apiDefinitionMapper.insert(test);
return test;
}
private void deleteFileByTestId(String apiId) {
ApiTestFileExample apiTestFileExample = new ApiTestFileExample();
apiTestFileExample.createCriteria().andTestIdEqualTo(apiId);
......@@ -276,4 +310,31 @@ public class ApiDefinitionService {
reportResult.setContent(result.getContent());
return reportResult;
}
public String apiTestImport(MultipartFile file, ApiTestImportRequest request) {
ApiImportParser apiImportParser = ApiImportParserFactory.getApiImportParser(request.getPlatform());
ApiDefinitionImport apiImport = null;
try {
apiImport = Objects.requireNonNull(apiImportParser).parseApi(file == null ? null : file.getInputStream(), request);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("parse_data_error"));
}
importApiTest(request, apiImport);
return "SUCCESS";
}
private void importApiTest(ApiTestImportRequest importRequest, ApiDefinitionImport apiImport) {
apiImport.getData().forEach(item -> {
item.setProjectId(importRequest.getProjectId());
item.setModuleId(importRequest.getModuleId());
item.setModulePath(importRequest.getModulePath());
item.setEnvironmentId(importRequest.getEnvironmentId());
item.setId(UUID.randomUUID().toString());
item.setUserId(null);
createTest(item);
});
}
}
......@@ -3,7 +3,7 @@
<ms-aside-container>
<ms-node-tree @selectModule="selectModule" @getApiModuleTree="initTree" @changeProject="changeProject" @changeProtocol="changeProtocol"
@refresh="refresh" @saveAsEdit="editApi"/>
@refresh="refresh" @saveAsEdit="editApi" @debug="debug" @exportAPI="exportAPI"/>
</ms-aside-container>
<ms-main-container>
......@@ -69,7 +69,7 @@
import MsApiConfig from "./components/ApiConfig";
import MsDebugHttpPage from "./components/debug/DebugHttpPage";
import MsRunTestHttpPage from "./components/runtest/RunTestHttpPage";
import {getCurrentUser, getUUID} from "../../../../common/js/utils";
import {downloadFile, getCurrentUser, getUUID} from "@/common/js/utils";
export default {
name: "TestCase",
......@@ -103,6 +103,11 @@
}],
}
},
watch: {
currentProtocol() {
this.handleCommand("closeAll");
}
},
methods: {
handleCommand(e) {
if (e === "add") {
......@@ -154,6 +159,9 @@
this.apiDefaultTab = newTabName;
}
},
debug() {
this.handleTabsEdit(this.$t('api_test.definition.request.fast_debug'), "debug");
},
editApi(row) {
this.currentApi = row;
this.handleTabsEdit(row.name, "add");
......@@ -168,6 +176,13 @@
selectModule(data) {
this.currentModule = data;
},
exportAPI() {
if (!this.$refs.apiList[0].tableData) {
return;
}
let obj = {projectName: this.currentProject.name, protocol: this.currentProtocol, data: this.$refs.apiList[0].tableData}
downloadFile("导出API.json", JSON.stringify(obj));
},
refresh(data) {
this.$refs.apiList[0].initApiTable(data);
},
......
......@@ -168,10 +168,10 @@
<script>
import MsTag from "../../../common/components/MsTag";
import MsTipButton from "../../../common/components/MsTipButton";
import MsApiRequestForm from "./request/ApiRequestForm";
import MsApiRequestForm from "./request/http/ApiRequestForm";
import {downloadFile, getUUID} from "@/common/js/utils";
import {parseEnvironment} from "../model/EnvironmentModel";
import ApiEnvironmentConfig from "../../test/components/ApiEnvironmentConfig";
import ApiEnvironmentConfig from "./environment/ApiEnvironmentConfig";
import {PRIORITY, RESULT_MAP} from "../model/JsonData";
import MsApiAssertions from "./assertion/ApiAssertions";
import MsRun from "./Run";
......
......@@ -5,26 +5,33 @@
<ms-add-complete-http-api @runTest="runTest" @saveApi="saveApi" :request="request" :headers="headers" :response="response" :basisData="currentApi"
:moduleOptions="moduleOptions" :currentProject="currentProject"
v-if="currentProtocol === 'HTTP'"/>
<!-- TCP -->
<ms-api-tcp-request-form :request="request" :currentProject="currentProject" :basisData="currentApi" :moduleOptions="moduleOptions" :maintainerOptions="maintainerOptions" v-if="currentProtocol === 'TCP'"/>
</div>
</template>
<script>
import MsAddCompleteHttpApi from "./complete/AddCompleteHttpApi";
import MsApiTcpRequestForm from "./complete/ApiTcpRequestForm";
import {ResponseFactory, Body} from "../model/ApiTestModel";
import {getUUID} from "@/common/js/utils";
import {createComponent, Request} from "./jmeter/components";
import Sampler from "./jmeter/components/sampler/sampler";
import HeaderManager from "./jmeter/components/configurations/header-manager";
import {WORKSPACE_ID} from '@/common/js/constants';
export default {
name: "ApiConfig",
components: {MsAddCompleteHttpApi},
components: {MsAddCompleteHttpApi, MsApiTcpRequestForm},
data() {
return {
reqUrl: "",
request: Sampler,
config: {},
response: {},
headers: [],
maintainerOptions: [],
}
},
props: {
......@@ -34,6 +41,7 @@
currentProtocol: String,
},
created() {
this.getMaintainerOptions();
switch (this.currentProtocol) {
case Request.TYPES.SQL:
this.request = createComponent("SQL");
......@@ -70,6 +78,13 @@
this.$emit('runTest', data);
});
},
getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.maintainerOptions = response.data;
});
},
createHttp() {
if (this.currentApi.request != undefined && this.currentApi.request != null) {
this.request = JSON.parse(this.currentApi.request);
......@@ -103,32 +118,36 @@
data.bodyUploadIds = [];
let request = data.request;
if (request.body) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
data.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
data.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
if (request.body.kvs) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
data.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
}
if (request.body.binary) {
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
data.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
}
}
return bodyUploadFiles;
},
......
......@@ -18,7 +18,20 @@
<el-input style="width: 175px; padding-left: 3px" :placeholder="$t('test_track.module.search')" v-model="filterText"
size="small">
<template v-slot:append>
<el-button icon="el-icon-folder-add" @click="addApi"></el-button>
<!--
<el-button icon="el-icon-folder-add" @click="addApi"></el-button>
-->
<el-dropdown size="small" split-button type="primary" class="ms-api-buttion" @click="handleCommand('add')"
@command="handleCommand">
<el-button icon="el-icon-folder-add" @click="addApi"></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="add-api">{{$t('api_test.definition.request.title')}}</el-dropdown-item>
<!--<el-dropdown-item command="add-module">{{$t('api_test.definition.request.add_module')}}</el-dropdown-item>-->
<el-dropdown-item command="debug">{{$t('api_test.definition.request.fast_debug')}}</el-dropdown-item>
<el-dropdown-item command="import">{{$t('api_test.api_import.label')}}</el-dropdown-item>
<el-dropdown-item command="export">{{$t('report.export')}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-input>
......@@ -81,6 +94,7 @@
</el-tree>
<ms-add-basis-http-api :current-protocol="value" ref="httpApi"></ms-add-basis-http-api>
<api-import ref="apiImport" @refresh="refresh"/>
</div>
......@@ -90,12 +104,14 @@
import MsAddBasisHttpApi from "./basis/AddBasisApi";
import SelectMenu from "../../../track/common/SelectMenu";
import {OPTIONS, DEFAULT_DATA} from "../model/JsonData";
import ApiImport from "./import/ApiImport";
export default {
name: 'MsApiModule',
components: {
MsAddBasisHttpApi,
SelectMenu
SelectMenu,
ApiImport
},
data() {
return {
......@@ -140,6 +156,24 @@
}
},
handleCommand(e) {
switch (e) {
case "debug":
this.$emit('debug');
break;
case "add-api":
this.addApi();
break;
case "add-module":
break;
case "import":
this.$refs.apiImport.open(this.currentModule);
break;
default:
this.$emit('exportAPI');
break;
}
},
buildNodePath(node, option, moduleOptions) {
//递归构建节点路径
option.id = node.id;
......@@ -382,7 +416,7 @@
return data.name.indexOf(value) !== -1;
},
addApi() {
this.$refs.httpApi.open(this.currentModule, this.currentProject.id);
this.$refs.httpApi.open(this.currentModule);
},
// 项目相关方法
changeProject(project) {
......@@ -460,4 +494,8 @@
/deep/ .el-tree-node__content {
height: 33px;
}
.ms-api-buttion {
width: 30px;
}
</style>
......@@ -74,7 +74,7 @@
</template>
<script>
import MsApiRequestForm from "../request/ApiRequestForm";
import MsApiRequestForm from "../request/http/ApiRequestForm";
import MsResponseText from "../response/ResponseText";
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import {REQ_METHOD, API_STATUS} from "../../model/JsonData";
......
<template xmlns:el-col="http://www.w3.org/1999/html">
<!-- 操作按钮 -->
<div style="background-color: white;">
<el-row>
<el-col>
<!--操作按钮-->
<div style="float: right;margin-right: 20px;margin-top: 20px">
<el-button type="primary" size="small" @click="validateApi">{{$t('commons.save')}}</el-button>
<el-button type="primary" size="small" @click="runTest">{{$t('commons.test')}}</el-button>
</div>
</el-col>
</el-row>
<!-- 基础信息 -->
<p class="tip">{{$t('test_track.plan_view.base_info')}} </p>
<br/>
<el-row>
<el-col>
<ms-basis-api :moduleOptions="moduleOptions" :basisData="basisData" ref="basicForm" @callback="saveApi"/>
</el-col>
</el-row>
<!-- 请求参数 -->
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<el-row>
<el-col :span="21" style="padding-bottom: 50px">
<el-form class="tcp" :model="request" :rules="rules" ref="request" label-width="auto" :disabled="isReadOnly">
<el-form-item/>
<!-- <el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input v-model="request.name" size="small" maxlength="300" show-word-limit/>
</el-form-item>-->
<el-form-item label="TCPClient" prop="classname">
<el-select v-model="request.classname" style="width: 100%" size="small">
<el-option v-for="c in classes" :key="c" :label="c" :value="c"/>
</el-select>
</el-form-item>
<el-row :gutter="10">
<el-col :span="16">
<el-form-item :label="$t('api_test.request.tcp.server')" prop="server">
<el-input v-model="request.server" maxlength="300" show-word-limit size="small"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="$t('api_test.request.tcp.port')" prop="port" label-width="60px">
<el-input-number v-model="request.port" controls-position="right" :min="0" :max="65535" size="small"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.connect')" prop="ctimeout">
<el-input-number v-model="request.ctimeout" controls-position="right" :min="0" size="small"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.response')" prop="timeout">
<el-input-number v-model="request.timeout" controls-position="right" :min="0" size="small"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.so_linger')" prop="soLinger">
<el-input v-model="request.soLinger" size="small"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.eol_byte')" prop="eolByte">
<el-input v-model="request.eolByte" size="small"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="6">
<el-form-item :label="$t('api_test.request.refer_to_environment')">
<el-switch
v-model="request.useEnvironment"
@change="useEnvironmentChange">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.re_use_connection')">
<el-checkbox v-model="request.reUseConnection"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.close_connection')">
<el-checkbox v-model="request.closeConnection"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.no_delay')">
<el-checkbox v-model="request.nodelay"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.username')" prop="username">
<el-input v-model="request.username" maxlength="100" show-word-limit size="small"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.password')" prop="password">
<el-input v-model="request.password" maxlength="30" show-word-limit show-password
autocomplete="new-password" size="small"/>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('api_test.request.tcp.request')" prop="request">
<div class="send-request">
<ms-code-edit mode="text" :read-only="isReadOnly" :data.sync="request.request"
:modes="['text', 'json', 'xml', 'html']" theme="eclipse"/>
</div>
</el-form-item>
</el-form>
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 30px">
<!-- 前置脚本 -->
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="warning"
:jsr223-processor="row"/>
<!--后置脚本-->
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="success"
:jsr223-processor="row"/>
<!--断言规则-->
<ms-api-assertions v-if="row.type==='Assertions'" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
<!--提取规则-->
<ms-api-extract :is-read-only="isReadOnly" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
</div>
</el-col>
<el-col :span="3" class="ms-left-cell">
<el-button class="ms-left-buttion" size="small" type="warning" @click="addPre" plain>+{{$t('api_test.definition.request.pre_script')}}</el-button>
<br/>
<el-button class="ms-left-buttion" size="small" type="success" @click="addPost" plain>+{{$t('api_test.definition.request.post_script')}}</el-button>
<br/>
<el-button class="ms-left-buttion" size="small" type="danger" @click="addAssertions" plain>+{{$t('api_test.definition.request.assertions_rule')}}</el-button>
<br/>
<el-button class="ms-left-buttion" size="small" type="info" @click="addExtract" plain>+{{$t('api_test.definition.request.extract_param')}}</el-button>
</el-col>
</el-row>
</div>
</template>
<script>
import MsApiAssertions from "../assertion/ApiAssertions";
import MsApiExtract from "../extract/ApiExtract";
import MsJsr233Processor from "../processor/Jsr233Processor";
import MsCodeEdit from "@/business/components/common/components/MsCodeEdit";
import TCPSampler from "../jmeter/components/sampler/tcp-sampler";
import {createComponent} from "../jmeter/components";
import {Assertions, Extract} from "../../model/ApiTestModel";
import {API_STATUS} from "../../model/JsonData";
import MsBasisApi from "./BasisApi";
export default {
name: "MsApiTcpRequestForm",
components: {MsCodeEdit, MsJsr233Processor, MsApiExtract, MsApiAssertions, MsBasisApi},
props: {
request: {},
basisData: {},
currentProject: {},
maintainerOptions: Array,
moduleOptions: Array,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
activeName: "assertions",
classes: TCPSampler.CLASSES,
rules: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
],
},
isReloadData: false,
options: API_STATUS,
}
},
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();
},
addPre() {
let jsr223PreProcessor = createComponent("JSR223PreProcessor");
this.request.hashTree.push(jsr223PreProcessor);
this.reload();
},
addPost() {
let jsr223PostProcessor = createComponent("JSR223PostProcessor");
this.request.hashTree.push(jsr223PostProcessor);
this.reload();
},
addAssertions() {
let assertions = new Assertions();
this.request.hashTree.push(assertions);
this.reload();
},
addExtract() {
let jsonPostProcessor = new Extract();
this.request.hashTree.push(jsonPostProcessor);
this.reload();
},
remove(row) {
let index = this.request.hashTree.indexOf(row);
this.request.hashTree.splice(index, 1);
this.reload();
},
reload() {
this.isReloadData = true
this.$nextTick(() => {
this.isReloadData = false
})
},
setParameter() {
this.request.modulePath = this.getPath(this.basisData.moduleId);
this.request.useEnvironment = undefined;
},
getPath(id) {
if (id === null) {
return null;
}
let path = this.moduleOptions.filter(function (item) {
return item.id === id ? item.path : "";
});
return path[0].path;
},
validateApi() {
this.basisData.method = "TCP";
if (this.currentProject === null) {
this.$error(this.$t('api_test.select_project'), 2000);
return;
}
this.$refs['basicForm'].validate();
},
saveApi() {
this.$emit('saveApi', this.request);
},
runTest() {
alert(444);
}
},
}
</script>
<style scoped>
.tcp >>> .el-input-number {
width: 100%;
}
.send-request {
padding: 0px 0;
height: 300px;
border: 1px #DCDFE6 solid;
border-radius: 4px;
width: 100%;
}
.ms-left-cell {
margin-top: 40px;
}
.ms-left-buttion {
margin: 6px 0px 8px 30px;
}
.ms-query {
background: #7F7F7F;
color: white;
height: 18px;
border-radius: 42%;
}
.ms-header {
background: #783887;
color: white;
height: 18px;
border-radius: 42%;
}
/deep/ .el-form-item {
margin-bottom: 15px;
}
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
margin: 0px 20px 0px;
}
</style>
<template>
<el-form :model="basicForm" label-position="right" label-width="80px" size="small" :rules="rule" ref="basicForm" style="margin-right: 20px">
<!-- 基础信息 -->
<el-row>
<el-col :span="12">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input class="ms-http-input" size="small" v-model="basicForm.name"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('test_track.module.module')" prop="moduleId">
<el-select class="ms-http-input" size="small" v-model="basicForm.moduleId" style="width: 100%">
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item :label="$t('commons.status')" prop="status">
<el-select class="ms-http-input" size="small" v-model="basicForm.status" style="width: 100%">
<el-option v-for="item in options" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('api_test.definition.request.responsible')" prop="userId">
<el-select v-model="basicForm.userId"
:placeholder="$t('api_test.definition.request.responsible')" filterable size="small"
class="ms-http-input" style="width: 100%">
<el-option
v-for="item in maintainerOptions"
:key="item.id"
:label="item.id + ' (' + item.name + ')'"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item :label="$t('commons.description')" prop="description">
<el-input class="ms-http-textarea"
v-model="basicForm.description"
type="textarea"
:autosize="{ minRows: 2, maxRows: 10}"
:rows="2" size="small"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
import {API_STATUS} from "../../model/JsonData";
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
export default {
name: "MsBasisApi",
components: {},
props: {
currentProtocol: {
type: String,
default: "HTTP"
},
moduleOptions: Array,
basisData: {},
},
created() {
this.getMaintainerOptions();
this.basicForm = this.basisData;
},
data() {
return {
basicForm: {},
httpVisible: false,
currentModule: {},
projectId: "",
maintainerOptions: [],
rule: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
],
userId: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
moduleId: [{required: true, message: this.$t('test_track.case.input_module'), trigger: 'change'}],
status: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}],
},
value: API_STATUS[0].id,
options: API_STATUS,
}
},
methods: {
getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.maintainerOptions = response.data;
});
},
validate() {
this.$refs['basicForm'].validate((valid) => {
if (valid) {
this.$emit('callback');
}
})
}
}
}
</script>
<style scoped>
</style>
......@@ -40,7 +40,7 @@
</template>
<script>
import MsApiRequestForm from "../request/ApiRequestForm";
import MsApiRequestForm from "../request/http/ApiRequestForm";
import MsResponseResult from "../response/ResponseResult";
import MsRequestMetric from "../response/RequestMetric";
import {getUUID, getCurrentUser} from "@/common/js/utils";
......
<template>
<el-dialog :close-on-click-modal="false" :title="$t('api_test.environment.environment_config')"
:visible.sync="visible" class="environment-dialog" width="60%"
@close="close" append-to-body ref="environmentConfig">
<el-container v-loading="result.loading">
<ms-aside-item :enable-aside-hidden="false" :title="$t('api_test.environment.environment_list')"
:data="environments" :item-operators="environmentOperators" :add-fuc="addEnvironment"
:delete-fuc="deleteEnvironment" @itemSelected="environmentSelected" ref="environmentItems"/>
<environment-edit :environment="currentEnvironment" ref="environmentEdit" @close="close"/>
</el-container>
</el-dialog>
</template>
<script>
import MsApiCollapse from "../collapse/ApiCollapse";
import MsApiCollapseItem from "../collapse/ApiCollapseItem";
import draggable from 'vuedraggable';
import MsContainer from "../../../../common/components/MsContainer";
import MsAsideContainer from "../../../../common/components/MsAsideContainer";
import MsMainContainer from "../../../../common/components/MsMainContainer";
import MsAsideItem from "../../../../common/components/MsAsideItem";
import EnvironmentEdit from "./EnvironmentEdit";
import {deepClone, listenGoBack, removeGoBackListener} from "@/common/js/utils";
import {Environment, parseEnvironment} from "../../model/EnvironmentModel";
export default {
name: "ApiEnvironmentConfig",
components: {
EnvironmentEdit,
MsAsideItem,
MsMainContainer, MsAsideContainer, MsContainer, MsApiCollapseItem, MsApiCollapse, draggable
},
data() {
return {
result: {},
visible: false,
projectId: '',
environments: [],
currentEnvironment: new Environment(),
environmentOperators: [
{
icon: 'el-icon-document-copy',
func: this.copyEnvironment
},
{
icon: 'el-icon-delete',
func: this.deleteEnvironment
}
]
}
},
methods: {
open: function (projectId) {
this.visible = true;
this.projectId = projectId;
this.getEnvironments();
listenGoBack(this.close);
},
deleteEnvironment(environment, index) {
if (environment.id) {
this.result = this.$get('/api/environment/delete/' + environment.id, () => {
this.$success(this.$t('commons.delete_success'));
this.getEnvironments();
});
}
else {
this.environments.splice(index, 1);
}
},
copyEnvironment(environment) {
this.currentEnvironment = environment;
if (!environment.id) {
this.$warning(this.$t('commons.please_save'));
return;
}
let newEnvironment = {};
newEnvironment = new Environment(environment);
newEnvironment.id = null;
newEnvironment.name = this.getNoRepeatName(newEnvironment.name);
if (!this.validateEnvironment(newEnvironment)) {
return;
}
this.$refs.environmentEdit._save(newEnvironment);
this.environments.push(newEnvironment);
this.$refs.environmentItems.itemSelected(this.environments.length - 1, newEnvironment);
},
validateEnvironment(environment) {
if (!this.$refs.environmentEdit.validate()) {
this.$error(this.$t('commons.formatErr'));
return false;
}
return true;
},
getNoRepeatName(name) {
for (let i in this.environments) {
if (this.environments[i].name === name) {
return this.getNoRepeatName(name + ' copy');
}
}
return name;
},
addEnvironment() {
let newEnvironment = new Environment({
projectId: this.projectId
});
this.environments.push(newEnvironment);
this.$refs.environmentItems.itemSelected(this.environments.length - 1, newEnvironment);
},
environmentSelected(environment) {
this.getEnvironment(environment);
},
getEnvironments() {
if (this.projectId) {
this.result = this.$get('/api/environment/list/' + this.projectId, response => {
this.environments = response.data;
if (this.environments.length > 0) {
this.$refs.environmentItems.itemSelected(0, this.environments[0]);
} else {
let item = new Environment({
projectId: this.projectId
});
this.environments.push(item);
this.$refs.environmentItems.itemSelected(0, item);
}
});
}
},
getEnvironment(environment) {
parseEnvironment(environment);
this.currentEnvironment = environment;
},
close() {
this.$emit('close');
this.visible = false;
this.$refs.environmentEdit.clearValidate();
removeGoBackListener(this.close);
}
}
}
</script>
<style scoped>
.environment-dialog >>> .el-dialog__body {
padding-top: 20px;
}
.el-container {
position: relative;
}
.ms-aside-container {
height: 100%;
position: absolute;
}
</style>
<template>
<div class="ms-border">
<el-table :data="hostTable" style="width: 100%" @cell-dblclick="dblHostTable" class="ht-tb">
<el-table-column prop="ip" label="IP">
<template slot-scope="scope">
<el-input v-if="scope.row.status" v-model="scope.row.ip"></el-input>
<span v-else>{{scope.row.ip}}</span>
</template>
</el-table-column>
<el-table-column prop="domain" :label="$t('load_test.domain')">
<template slot-scope="scope">
<el-input v-if="scope.row.status" v-model="scope.row.domain"></el-input>
<span v-else>{{scope.row.domain}}</span>
</template>
</el-table-column>
<el-table-column prop="annotation" :label="$t('commons.annotation')">
<template slot-scope="scope">
<el-input v-if="scope.row.status" v-model="scope.row.annotation"></el-input>
<span v-else>{{scope.row.annotation}}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" width="100">
<template v-slot:default="scope">
<span>
<el-button size="mini" p="$t('commons.remove')" icon="el-icon-close" circle @click="remove(scope.row)"
class="ht-btn-remove"/>
<el-button size="mini" p="$t('commons.save')" icon="el-icon-check" circle @click="confirm(scope.row)"
class="ht-btn-confirm"/>
</span>
</template>
</el-table-column>
</el-table>
<el-button class="ht-btn-add" size="mini" p="$t('commons.add')" icon="el-icon-circle-plus-outline" @click="add">添加
</el-button>
</div>
</template>
<script>
import MsApiVariableInput from "../ApiVariableInput";
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
export default {
name: "MsApiHostTable",
components: {MsApiVariableInput, MsTableOperatorButton},
props: {
hostTable: Array,
},
data() {
return {
currentPage: 1,
pageSize: 5,
total: 0,
}
},
created() {
this.init();
},
methods: {
remove: function (row) {
this.hostTable.splice(this.hostTable.indexOf(row), 1);
},
change: function () {
this.$emit('change', this.hostTable);
},
add: function (r) {
let row = {
ip: '',
domain: '',
status: 'edit',
annotation: '',
uuid: this.uuid(),
}
// 获取上一行的数据
for (let i = this.hostTable.length - 1; i >= 0; i--) {
if (this.hostTable[i].status === '') {
row.ip = this.hostTable[i].ip;
row.domain = this.hostTable[i].domain;
row.annotation = this.hostTable[i].annotation;
break;
}
}
this.hostTable.push(row);
},
confirm: function (row) {
this.validateIp(row.ip) && this.validateDomain(row.domain) ? row.status = '' : row.status;
this.$emit('change', this.hostTable);
if (row.status === "") {
return true;
}
return false;
},
init: function () {
if (this.hostTable === undefined || this.hostTable.length === 0) {
let row = {
ip: '',
domain: '',
status: 'edit',
annotation: '',
uuid: this.uuid()
}
this.hostTable.push(row);
}
},
validateIp(ip) {
let regexp = /^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$/;
if (regexp.test(ip) == false) {
this.$warning(this.$t('load_test.input_ip'));
return false;
}
return true;
},
validateDomain(domain) {
let strRegex = "^(?=^.{3,255}$)(http(s)?:\\/\\/)?(www\\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\\d+)*(\\/\\w+\\.\\w+)*$";
const re = new RegExp(strRegex);
if (re.test(domain) && domain.length < 67) {
return true;
}
this.$warning(this.$t('load_test.input_domain'));
return false;
},
dblHostTable: function (row) {
row.status = 'edit';
},
uuid: function () {
return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1);
},
}
}
</script>
<style scoped>
.ht-btn-remove {
color: white;
background-color: #DCDFE6;
}
.ht-btn-confirm {
color: white;
background-color: #1483F6;
}
.ht-btn-add {
border: 0px;
margin-top: 10px;
color: #1483F6;
background-color: white;
}
.ht-tb {
background-color: white;
border: 0px;
}
</style>
<template>
<div>
<span class="kv-description" v-if="description">
{{description}}
</span>
<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 class="kv-checkbox">
<input type="checkbox" v-if="!isDisable(index)" @change="change" :value="item.uuid" v-model="item.enable"
:disabled="isDisable(index) || isReadOnly"/>
</el-col>
<el-col>
<ms-api-variable-input :show-copy="showCopy" :show-variable="showVariable" :is-read-only="isReadOnly" v-model="item.name" size="small" maxlength="200" @change="change"
:placeholder="$t('api_test.variable_name')" show-word-limit/>
</el-col>
<el-col>
<el-input :disabled="isReadOnly" v-model="item.value" size="small" @change="change"
:placeholder="$t('api_test.value')" show-word-limit/>
</el-col>
<el-col class="kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index) || isReadOnly"/>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import {KeyValue} from "../../model/ApiTestModel";
import MsApiVariableInput from "../ApiVariableInput";
export default {
name: "MsApiScenarioVariables",
components: {MsApiVariableInput},
props: {
description: String,
items: Array,
isReadOnly: {
type: Boolean,
default: false
},
showVariable: {
type: Boolean,
default: true
},
showCopy: {
type: Boolean,
default: true
},
},
data() {
return {
}
},
methods: {
remove: function (index) {
this.items.splice(index, 1);
this.$emit('change', this.items);
},
change: function () {
let isNeedCreate = true;
let removeIndex = -1;
this.items.forEach((item, index) => {
if (!item.name && !item.value) {
// 多余的空行
if (index !== this.items.length - 1) {
removeIndex = index;
}
// 没有空行,需要创建空行
isNeedCreate = false;
}
});
if (isNeedCreate) {
this.items.push(new KeyValue({enable: true}));
}
this.$emit('change', this.items);
// TODO 检查key重复
},
isDisable: function (index) {
return this.items.length - 1 === index;
}
},
created() {
if (this.items.length === 0) {
this.items.push(new KeyValue({enable: true}));
}
}
}
</script>
<style scoped>
.kv-description {
font-size: 13px;
}
.kv-checkbox {
width: 20px;
margin-right: 10px;
}
.kv-row {
margin-top: 10px;
}
.kv-delete {
width: 60px;
}
</style>
<template>
<div>
<el-form :model="commonConfig" :rules="rules" ref="commonConfig">
<span>{{$t('api_test.environment.globalVariable')}}</span>
<ms-api-scenario-variables :show-copy="false" :items="commonConfig.variables"/>
<el-form-item>
<el-switch v-model="commonConfig.enableHost" active-text="Hosts"/>
</el-form-item>
<ms-api-host-table v-if="commonConfig.enableHost" :hostTable="commonConfig.hosts" ref="refHostTable"/>
</el-form>
</div>
</template>
<script>
import {CommonConfig, Environment} from "../../model/EnvironmentModel";
import MsApiScenarioVariables from "./ApiScenarioVariables";
import MsApiHostTable from "./ApiHostTable";
export default {
name: "MsEnvironmentCommonConfig",
components: {MsApiHostTable, MsApiScenarioVariables},
props: {
commonConfig: new CommonConfig(),
},
data() {
return {
rules: {
},
}
},
methods: {
validate() {
let isValidate = false;
this.$refs['commonConfig'].validate((valid) => {
if (valid) {
// 校验host列表
let valHost = true;
if (this.commonConfig.enableHost) {
for (let i = 0; i < this.commonConfig.hosts.length; i++) {
valHost = this.$refs['refHostTable'].confirm(this.commonConfig.hosts[i]);
}
}
if (valHost) {
isValidate = true;
} else {
isValidate = false;
}
} else {
isValidate = false;
}
});
return isValidate;
}
}
}
</script>
<style scoped>
</style>
<template>
<el-main v-loading="result.loading">
<el-form :model="environment" :rules="rules" ref="environment">
<span>{{$t('api_test.environment.name')}}</span>
<el-form-item prop="name">
<el-input v-model="environment.name" :placeholder="this.$t('commons.input_name')" clearable/>
</el-form-item>
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.environment.common_config')" name="common">
<ms-environment-common-config :common-config="environment.config.commonConfig" ref="commonConfig"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.http_config')" name="http">
<ms-environment-http-config :http-config="environment.config.httpConfig" ref="httpConfig"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.database_config')" name="sql">
<ms-database-config :configs="environment.config.databaseConfigs"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
<ms-tcp-config :config="environment.config.tcpConfig"/>
</el-tab-pane>
</el-tabs>
<div class="environment-footer">
<ms-dialog-footer
@cancel="cancel"
@confirm="save()"/>
</div>
</el-form>
</el-main>
</template>
<script>
import MsApiScenarioVariables from "./ApiScenarioVariables";
import MsApiKeyValue from "../ApiKeyValue";
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {REQUEST_HEADERS} from "@/common/js/constants";
import {Environment} from "../../model/EnvironmentModel";
import MsApiHostTable from "./ApiHostTable";
import MsDatabaseConfig from "../request/database/DatabaseConfig";
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
import MsTcpConfig from "../request/tcp/TcpConfig";
export default {
name: "EnvironmentEdit",
components: {
MsTcpConfig,
MsEnvironmentCommonConfig,
MsEnvironmentHttpConfig,
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables},
props: {
environment: new Environment(),
},
data() {
return {
result: {},
envEnable: false,
rules: {
name: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 64, message: this.$t('commons.input_limit', [1, 64]), trigger: 'blur'}
],
},
headerSuggestions: REQUEST_HEADERS,
activeName: 'common'
}
},
watch: {
environment: function (o) {
this.envEnable = o.enable;
}
},
methods: {
save() {
this.$refs['environment'].validate((valid) => {
if (valid && this.$refs.commonConfig.validate() && this.$refs.httpConfig.validate()) {
this._save(this.environment);
}
});
},
validate() {
let isValidate = false;
this.$refs['environment'].validate((valid) => {
if (valid && this.$refs.commonConfig.validate() && this.$refs.httpConfig.validate()) {
isValidate = true;
} else {
isValidate = false;
}
});
return isValidate;
},
_save(environment) {
let param = this.buildParam(environment);
let url = '/api/environment/add';
if (param.id) {
url = '/api/environment/update';
}
this.result = this.$post(url, param, response => {
if (!param.id) {
environment.id = response.data;
}
this.$success(this.$t('commons.save_success'));
});
},
buildParam: function (environment) {
let param = {};
Object.assign(param, environment);
let hosts = param.config.commonConfig.hosts;
if (hosts != undefined) {
let validHosts = [];
// 去除掉未确认的host
hosts.forEach(host => {
if (host.status === '') {
validHosts.push(host);
}
});
param.config.commonConfig.hosts = validHosts;
}
param.config = JSON.stringify(param.config);
return param;
},
cancel() {
this.$emit('close');
},
clearValidate() {
this.$refs["environment"].clearValidate();
},
},
}
</script>
<style scoped>
.el-main {
border: solid 1px #EBEEF5;
margin-left: 200px;
min-height: 400px;
max-height: 700px;
}
.el-row {
margin-bottom: 15px;
}
.environment-footer {
margin-top: 15px;
float: right;
}
span {
display: block;
margin-bottom: 15px;
}
span:not(:first-child) {
margin-top: 15px;
}
</style>
<template>
<el-form :model="httpConfig" :rules="rules" ref="httpConfig">
<span>{{$t('api_test.environment.socket')}}</span>
<el-form-item prop="socket">
<el-input v-model="httpConfig.socket" :placeholder="$t('api_test.request.url_description')" clearable>
<template v-slot:prepend>
<el-select v-model="httpConfig.protocol" class="request-protocol-select">
<el-option label="http://" value="http"/>
<el-option label="https://" value="https"/>
</el-select>
</template>
</el-input>
</el-form-item>
<span>{{$t('api_test.request.headers')}}</span>
<ms-api-key-value :items="httpConfig.headers" :isShowEnable="true" :suggestions="headerSuggestions"/>
</el-form>
</template>
<script>
import {HttpConfig} from "../../model/EnvironmentModel";
import MsApiKeyValue from "../ApiKeyValue";
import {REQUEST_HEADERS} from "../../../../../../common/js/constants";
export default {
name: "MsEnvironmentHttpConfig",
components: {MsApiKeyValue},
props: {
httpConfig: new HttpConfig(),
},
data() {
let socketValidator = (rule, value, callback) => {
if (!this.validateSocket(value)) {
callback(new Error(this.$t('commons.formatErr')));
return false;
} else {
callback();
return true;
}
}
return {
headerSuggestions: REQUEST_HEADERS,
rules: {
socket: [{required: false, validator: socketValidator, trigger: 'blur'}],
},
}
},
methods: {
validateSocket(socket) {
if (!socket) return true;
let urlStr = this.httpConfig.protocol + '://' + socket;
let url = {};
try {
url = new URL(urlStr);
} catch (e) {
return false;
}
this.httpConfig.domain = decodeURIComponent(url.hostname);
this.httpConfig.port = url.port;
let path = url.pathname === '/' ? '' : url.pathname;
if (url.port) {
this.httpConfig.socket = this.httpConfig.domain + ':' + url.port + path;
} else {
this.httpConfig.socket = this.httpConfig.domain + path;
}
return true;
},
validate() {
let isValidate = false;
this.$refs['httpConfig'].validate((valid) => {
isValidate = valid;
});
return isValidate;
}
}
}
</script>
<style scoped>
.request-protocol-select {
width: 90px;
}
</style>
<template>
<el-dialog :close-on-click-modal="false" :title="$t('api_test.api_import.title')" :visible.sync="visible" class="api-import" v-loading="result.loading" @close="close">
<div class="header-bar">
<div>{{$t('api_test.api_import.data_format')}}</div>
<el-radio-group v-model="selectedPlatformValue">
<el-radio v-for="(item, index) in platforms" :key="index" :label="item.value">{{item.name}}</el-radio>
</el-radio-group>
<div class="operate-button">
<el-button class="save-button" type="primary" plain @click="save">
{{$t('commons.save')}}
</el-button>
<el-button class="cancel-button" type="warning" plain @click="visible = false">
{{$t('commons.cancel')}}
</el-button>
</div>
</div>
<el-form :model="formData" :rules="rules" label-width="100px" v-loading="result.loading" ref="form">
<el-row>
<el-col :span="11">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input size="small" class="name-input" v-model="formData.name" clearable show-word-limit/>
</el-form-item>
<el-form-item :label="$t('commons.project')" prop="projectId">
<el-select size="small" v-model="formData.projectId" class="project-select" clearable>
<el-option v-for="(project, index) in projects" :key="index" :label="project.name" :value="project.id"/>
</el-select>
</el-form-item>
<el-form-item v-if="useEnvironment || selectedPlatformValue == 'Swagger2'" :label="$t('api_test.environment.environment_config')" prop="environmentId">
<el-select v-if="showEnvironmentSelect" size="small" v-model="formData.environmentId" class="environment-select" clearable>
<el-option v-for="(environment, index) in environments" :key="index" :label="environment.name" :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 :disabled="formData.projectId == ''" class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">{{$t('api_test.environment.environment_config')}}</el-button>
</div>
</template>
</el-select>
</el-form-item>
<el-form-item v-if="selectedPlatformValue != 'Swagger2'" prop="useEnvironment">
<el-checkbox v-model="useEnvironment">{{$t('api_test.environment.config_environment')}}</el-checkbox>
</el-form-item>
<el-form-item :label="'Swagger URL'" prop="wgerUrl" v-if="selectedPlatformValue == 'Swagger2' && swaggerUrlEable">
<el-input size="small" v-model="formData.swaggerUrl" clearable show-word-limit/>
</el-form-item>
<el-form-item v-if="selectedPlatformValue == 'Swagger2'">
<el-switch
v-model="swaggerUrlEable"
:active-text="$t('api_test.api_import.swagger_url_import')">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="1" v-if="selectedPlatformValue != 'Swagger2' || (selectedPlatformValue == 'Swagger2' && !swaggerUrlEable)">
<el-divider direction="vertical"/>
</el-col>
<el-col :span="12" v-if="selectedPlatformValue != 'Swagger2' || (selectedPlatformValue == 'Swagger2' && !swaggerUrlEable)">
<el-upload
class="api-upload"
drag
action=""
:http-request="upload"
:limit="1"
:beforeUpload="uploadValidate"
:on-remove="handleRemove"
:file-list="fileList"
:on-exceed="handleExceed"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
<div class="el-upload__tip" slot="tip">{{$t('api_test.api_import.file_size_limit')}}</div>
</el-upload>
</el-col>
</el-row>
</el-form>
<div class="format-tip">
<div>
<span>{{$t('api_test.api_import.tip')}}:{{selectedPlatform.tip}}</span>
</div>
<div>
<span>{{$t('api_test.api_import.export_tip')}}:{{selectedPlatform.exportTip}}</span>
</div>
</div>
<api-environment-config ref="environmentConfig" @close="getEnvironments"/>
</el-dialog>
</template>
<script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import ApiEnvironmentConfig from "../environment/ApiEnvironmentConfig";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
export default {
name: "ApiImport",
components: {ApiEnvironmentConfig, MsDialogFooter},
data() {
return {
visible: false,
swaggerUrlEable: false,
showEnvironmentSelect: true,
platforms: [
{
name: 'Metersphere',
value: 'Metersphere',
tip: this.$t('api_test.api_import.ms_tip'),
exportTip: this.$t('api_test.api_import.ms_export_tip'),
suffixes: new Set(['json'])
},
{
name: 'Postman',
value: 'Postman',
tip: this.$t('api_test.api_import.postman_tip'),
exportTip: this.$t('api_test.api_import.post_export_tip'),
suffixes: new Set(['json'])
},
{
name: 'Swagger',
value: 'Swagger2',
tip: this.$t('api_test.api_import.swagger_tip'),
exportTip: this.$t('api_test.api_import.swagger_export_tip'),
suffixes: new Set(['json'])
}
],
selectedPlatform: {},
selectedPlatformValue: 'Metersphere',
result: {},
projects: [],
environments: [],
useEnvironment: false,
formData: {
name: '',
environmentId: '',
projectId: '',
file: undefined,
swaggerUrl: ''
},
currentModule: {},
rules: {
name: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 60, message: this.$t('commons.input_limit', [1, 60]), trigger: 'blur'}
],
environmentId: [
{required: true, message: this.$t('api_test.environment.select_environment'), trigger: 'blur'},
],
projectId: [
{required: true, message: this.$t('api_test.select_project'), trigger: 'blur'},
]
},
fileList: []
}
},
activated() {
this.selectedPlatform = this.platforms[0];
this.getProjects();
},
watch: {
selectedPlatformValue() {
for (let i in this.platforms) {
if (this.platforms[i].value === this.selectedPlatformValue) {
this.selectedPlatform = this.platforms[i];
break;
}
}
},
'formData.projectId'() {
this.getEnvironments();
}
},
methods: {
open(module) {
this.currentModule = module;
this.visible = true;
listenGoBack(this.close);
},
upload(file) {
this.formData.file = file.file;
},
handleExceed(files, fileList) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
},
handleRemove(file, fileList) {
this.formData.file = undefined;
},
uploadValidate(file, fileList) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (!this.selectedPlatform.suffixes.has(suffix)) {
this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false;
}
if (file.size / 1024 / 1024 > 20) {
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
return false;
}
return true;
},
getEnvironments() {
if (this.formData.projectId) {
this.$get('/api/environment/list/' + this.formData.projectId, response => {
this.environments = response.data;
let hasEnvironmentId = false;
this.environments.forEach(env => {
if (env.id === this.formData.environmentId) {
hasEnvironmentId = true;
}
});
if (!hasEnvironmentId) {
this.formData.environmentId = '';
}
});
} else {
this.environments = [];
this.formData.environmentId = '';
}
},
getProjects() {
this.result = this.$get("/project/listAll", response => {
this.projects = response.data;
})
},
openEnvironmentConfig() {
if (!this.formData.projectId) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.showEnvironmentSelect = false;
this.$refs.environmentConfig.open(this.formData.projectId);
this.showEnvironmentSelect = true;
},
save() {
this.$refs.form.validate(valid => {
if (valid) {
let param = {};
Object.assign(param, this.formData);
param.platform = this.selectedPlatformValue;
param.useEnvironment = this.useEnvironment;
param.moduleId = this.currentModule.id;
param.modulePath = this.currentModule.path;
if ((this.selectedPlatformValue != 'Swagger2' || (this.selectedPlatformValue == 'Swagger2' && !this.swaggerUrlEable)) && !this.formData.file) {
this.$warning(this.$t('commons.please_upload'));
return;
}
if (!this.swaggerUrlEable) {
param.swaggerUrl = undefined;
}
this.result = this.$fileUpload('/api/definition/import', param.file, null, param, response => {
let res = response.data;
this.$success(this.$t('test_track.case.import.success'));
this.visible = false;
this.$emit('refresh');
});
} else {
return false;
}
});
},
close() {
this.formData = {
name: '',
environmentId: '',
projectId: '',
file: undefined,
swaggerUrl: ''
};
this.fileList = [];
removeGoBackListener(this.close);
this.visible = false;
}
}
}
</script>
<style scoped>
.format-tip {
background: #EDEDED;
}
.api-upload {
text-align: center;
margin: auto 0;
}
.api-upload >>> .el-upload {
width: 100%;
max-width: 350px;
}
.api-upload >>> .el-upload-dragger {
width: 100%;
}
.el-radio-group {
margin: 10px 0;
}
.header-bar, .format-tip, .el-form {
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
.header-bar {
padding: 10px 30px;
}
.api-import >>> .el-dialog__body {
padding: 15px 25px;
}
.operate-button {
float: right;
}
.save-button {
margin-left: 10px;
}
.environment-button {
margin-left: 20px;
padding: 7px;
}
.empty-environment {
padding: 10px 0px;
}
.el-form {
padding: 30px 10px;
}
.el-divider {
height: 200px;
}
.name-input {
max-width: 195px;
}
.dialog-footer {
float: right;
}
</style>
......@@ -12,27 +12,26 @@ const DEFAULT_OPTIONS = {
};
export default class TCPSampler extends Sampler {
static CLASSES = ["TCPClientImpl", "BinaryTCPClientImpl", "LengthPrefixedBinaryTCPClientImpl"]
constructor(options = DEFAULT_OPTIONS) {
super(options);
this.classname = this.initStringProp("TCPSampler.classname", "TCPClientImpl")
this.server = this.initStringProp("TCPSampler.server")
this.port = this.initStringProp("TCPSampler.port")
this.connectTimeout = this.initStringProp("TCPSampler.ctimeout")
this.responseTimeout = this.initStringProp("TCPSampler.timeout")
this.reUseConnection = this.initBoolProp("TCPSampler.reUseConnection", true)
this.closeConnection = this.initBoolProp("TCPSampler.closeConnection", false)
this.nodelay = this.initBoolProp("TCPSampler.nodelay", false)
this.soLinger = this.initStringProp("TCPSampler.soLinger")
this.eolByte = this.initStringProp("TCPSampler.EolByte")
this.request = this.initStringProp("TCPSampler.request")
this.username = this.initStringProp("ConfigTestElement.username")
this.password = this.initStringProp("ConfigTestElement.password")
this.type = "TCPSampler";
this.classname = options.classname || TCPSampler.CLASSES[0];
this.server = options.server;
this.port = options.port;
this.ctimeout = options.ctimeout; // Connect
this.timeout = options.timeout; // Response
this.reUseConnection = options.reUseConnection === undefined ? true : options.reUseConnection;
this.nodelay = options.nodelay === undefined ? false : options.nodelay;
this.closeConnection = options.closeConnection === undefined ? false : options.closeConnection;
this.soLinger = options.soLinger;
this.eolByte = options.eolByte;
this.username = options.username;
this.password = options.password;
this.hashTree = [];
}
}
......
<template>
<div>
<ms-database-from :config="currentConfig" :callback="saveConfig" ref="databaseFrom" :is-read-only="isReadOnly"/>
<ms-database-config-list @rowSelect="rowSelect" v-if="configs.length > 0" :table-data="configs"/>
</div>
</template>
<script>
import MsDatabaseConfigList from "./DatabaseConfigList";
import {DatabaseConfig} from "../../../model/ApiTestModel";
import MsDatabaseFrom from "./DatabaseFrom";
import {getUUID} from "@/common/js/utils";
export default {
name: "MsDatabaseConfig",
components: {MsDatabaseFrom, MsDatabaseConfigList},
props: {
configs: Array,
isReadOnly: {
type: Boolean,
default: false
},
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
currentConfig: new DatabaseConfig()
}
},
watch: {
configs() {
this.currentConfig = new DatabaseConfig();
}
},
methods: {
saveConfig(config) {
for (let item of this.configs) {
if (item.name === config.name && item.id !== config.id) {
this.$warning(this.$t('commons.already_exists'));
return;
}
}
if (config.id) {
this.updateConfig(config);
} else {
this.addConfig(config);
}
},
addConfig(config) {
config.id = getUUID();
let item = {};
Object.assign(item, config);
this.configs.push(item);
this.currentConfig = new DatabaseConfig();
},
updateConfig(config) {
Object.assign(this.currentConfig, config);
this.currentConfig = new DatabaseConfig();
},
rowSelect(config) {
this.currentConfig = config;
}
}
}
</script>
<style scoped>
.addButton {
float: right;
}
.database-from {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>
<template>
<div class="database-config-list">
<el-table border :data="tableData"
class="adjust-table table-content"
highlight-current-row
@row-click="handleView">
<el-table-column prop="name" :label="$t('api_test.request.sql.dataSource')" show-overflow-tooltip/>
<el-table-column prop="driver" :label="$t('api_test.request.sql.database_driver')" show-overflow-tooltip/>
<el-table-column prop="dbUrl" :label="$t('api_test.request.sql.database_url')" show-overflow-tooltip/>
<el-table-column prop="username" :label="$t('api_test.request.sql.username')" show-overflow-tooltip/>
<el-table-column prop="poolMax" :label="$t('api_test.request.sql.pool_max')" show-overflow-tooltip/>
<el-table-column prop="timeout" :label="$t('api_test.request.sql.query_timeout')" show-overflow-tooltip/>
<el-table-column :label="$t('commons.operating')" min-width="100">
<template v-slot:default="scope">
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.copy')" icon="el-icon-document-copy" type="success" @exec="handleCopy(scope.$index, scope.row)"/>
<ms-table-operator-button :isTesterPermission="true" :tip="$t('commons.delete')" icon="el-icon-delete" type="danger" @exec="handleDelete(scope.$index)"/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import {DatabaseConfig} from "../../../model/ApiTestModel";
import MsTableOperator from "../../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../../common/components/MsTableOperatorButton";
import {getUUID} from "../../../../../../../common/js/utils";
export default {
name: "MsDatabaseConfigList",
components: {MsTableOperatorButton, MsTableOperator},
props: {
tableData: Array,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
result: {},
}
},
methods: {
handleView(row) {
this.$emit('rowSelect', row);
},
handleDelete(index) {
this.tableData.splice(index, 1);
},
handleCopy(index, config) {
let copy = {};
Object.assign(copy, config);
copy.id = getUUID();
copy.name = this.getNoRepeatName(copy.name);
this.tableData.splice(index + 1, 0, copy);
},
getNoRepeatName(name) {
for (let i in this.tableData) {
if (this.tableData[i].name === name) {
return this.getNoRepeatName(name + ' copy');
}
}
return name;
},
}
}
</script>
<style scoped>
.addButton {
float: right;
}
</style>
<template>
<div class="database-from" v-loading="result.loading">
<el-form :model="currentConfig" :rules="rules" label-width="150px" size="small" :disabled="isReadOnly" ref="databaseFrom">
<el-form-item :label="$t('api_test.request.sql.dataSource')" prop="name">
<el-input v-model="currentConfig.name" maxlength="300" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="$t('api_test.request.sql.database_url')" prop="dbUrl">
<el-input v-model="currentConfig.dbUrl" maxlength="500" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="$t('api_test.request.sql.database_driver')" prop="driver">
<el-select v-model="currentConfig.driver" class="select-100" clearable>
<el-option v-for="p in drivers" :key="p" :label="p" :value="p"/>
</el-select>
</el-form-item>
<el-form-item :label="$t('api_test.request.sql.username')" prop="username">
<el-input v-model="currentConfig.username" maxlength="300" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="$t('api_test.request.sql.password')" prop="password">
<el-input v-model="currentConfig.password" type="password" autocomplete="new-password" maxlength="200"
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="$t('api_test.request.sql.pool_max')" prop="poolMax">
<el-input-number size="small" :disabled="isReadOnly" v-model="currentConfig.poolMax" :placeholder="$t('commons.please_select')" :max="1000*10000000" :min="0"/>
</el-form-item>
<el-form-item :label="$t('api_test.request.sql.timeout')" prop="timeout">
<el-input-number size="small" :disabled="isReadOnly" v-model="currentConfig.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
</el-form-item>
<el-form-item>
<div class="buttons">
<el-button type="primary" v-show="currentConfig.id" size="small" @click="validate()">{{$t('commons.validate')}}</el-button>
<el-button type="primary" v-show="currentConfig.id" size="small" @click="save('update')">{{$t('commons.update')}}</el-button>
<el-button type="primary" size="small" @click="save('add')">{{$t('commons.add')}}</el-button>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {DatabaseConfig} from "../../../model/ApiTestModel";
export default {
name: "MsDatabaseFrom",
components: {},
props: {
isReadOnly: {
type: Boolean,
default: false
},
config: {
type: Object,
default() {
return new DatabaseConfig();
}
},
callback: {
type: Function
},
},
watch: {
config() {
Object.assign(this.currentConfig, this.config);
}
},
mounted() {
Object.assign(this.currentConfig, this.config);
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
result: {},
currentConfig: new DatabaseConfig(),
rules: {
name: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
driver: [
{required: true, message: this.$t('commons.cannot_be_null'), trigger: 'blur'},
],
password: [
{max: 200, message: this.$t('commons.input_limit', [0, 200]), trigger: 'blur'}
],
dbUrl: [
{required: true, message: this.$t('commons.cannot_be_null'), trigger: 'blur'},
{max: 500, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'}
],
username: [
{required: true, message: this.$t('commons.cannot_be_null'), trigger: 'blur'},
{max: 200, message: this.$t('commons.input_limit', [0, 200]), trigger: 'blur'}
],
poolMax: [
{required: true, message: this.$t('commons.cannot_be_null'), trigger: 'blur'},
],
timeout: [
{required: true, message: this.$t('commons.cannot_be_null'), trigger: 'blur'},
]
}
}
},
methods: {
save(type) {
this.$refs['databaseFrom'].validate((valid) => {
if (valid) {
if (this.callback) {
if (type === 'add') {
this.currentConfig.id = undefined;
}
this.callback(this.currentConfig);
}
} else {
return false;
}
});
},
validate() {
this.result = this.$post('/api/database/validate', this.currentConfig, () => {
this.$success(this.$t('commons.connection_successful'));
})
}
}
}
</script>
<style scoped>
.buttons {
float: right;
}
</style>
......@@ -87,18 +87,18 @@
</template>
<script>
import MsApiKeyValue from "../ApiKeyValue";
import MsApiBody from "../body/ApiBody";
import MsApiAuthConfig from "../auth/ApiAuthConfig";
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
import MsApiKeyValue from "../../ApiKeyValue";
import MsApiBody from "../../body/ApiBody";
import MsApiAuthConfig from "../../auth/ApiAuthConfig";
import ApiRequestMethodSelect from "../../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "../ApiVariable";
import MsJsr233Processor from "../processor/Jsr233Processor";
import MsApiAdvancedConfig from "../ApiAdvancedConfig";
import {createComponent} from "../jmeter/components";
import MsApiAssertions from "../assertion/ApiAssertions";
import MsApiExtract from "../extract/ApiExtract";
import {Assertions, Extract} from "../../model/ApiTestModel";
import MsApiVariable from "../../ApiVariable";
import MsJsr233Processor from "../../processor/Jsr233Processor";
import MsApiAdvancedConfig from "../../ApiAdvancedConfig";
import {createComponent} from "../../jmeter/components";
import MsApiAssertions from "../../assertion/ApiAssertions";
import MsApiExtract from "../../extract/ApiExtract";
import {Assertions, Extract} from "../../../model/ApiTestModel";
export default {
name: "MsApiHttpRequestForm",
......
......@@ -6,11 +6,6 @@
<script>
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
import {Request} from "../jmeter/components";
// import MsApiTcpRequestForm from "./ApiTcpRequestForm";
// import MsApiDubboRequestForm from "./ApiDubboRequestForm";
// import MsApiSqlRequestForm from "./ApiSqlRequestForm";
export default {
name: "MsApiRequestForm",
......@@ -29,21 +24,7 @@
},
computed: {
component({request: {type}}) {
let name;
switch (type) {
case Request.TYPES.DUBBO:
name = "MsApiDubboRequestForm";
break;
case Request.TYPES.SQL:
name = "MsApiSqlRequestForm";
break;
case Request.TYPES.TCP:
name = "MsApiTcpRequestForm";
break;
default:
name = "MsApiHttpRequestForm";
}
return name;
return "MsApiHttpRequestForm";
}
}
}
......
<template>
<el-form class="tcp" :model="config" :rules="rules" ref="config" label-width="120px" :disabled="isReadOnly"
size="small">
<el-form-item label="TCPClient" prop="classname">
<el-select v-model="config.classname" style="width: 100%">
<el-option v-for="c in classes" :key="c" :label="c" :value="c"/>
</el-select>
</el-form-item>
<el-row :gutter="10">
<el-col :span="16">
<el-form-item :label="$t('api_test.request.tcp.server')" prop="server">
<el-input v-model="config.server" maxlength="300" show-word-limit/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="$t('api_test.request.tcp.port')" prop="port" label-width="60px">
<el-input-number v-model="config.port" controls-position="right" :min="0" :max="65535" :controls="false"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.connect')" prop="ctimeout">
<el-input-number v-model="config.ctimeout" controls-position="right" :min="0" :step="1000" :controls="false"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.response')" prop="timeout">
<el-input-number v-model="config.timeout" controls-position="right" :min="0" :step="1000" :controls="false"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.so_linger')" prop="soLinger">
<el-input v-model="config.soLinger"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.eol_byte')" prop="eolByte">
<el-input v-model="config.eolByte"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="8">
<el-form-item :label="$t('api_test.request.tcp.re_use_connection')">
<el-checkbox v-model="config.reUseConnection"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="$t('api_test.request.tcp.close_connection')">
<el-checkbox v-model="config.closeConnection"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="$t('api_test.request.tcp.no_delay')">
<el-checkbox v-model="config.nodelay"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.username')" prop="username">
<el-input v-model="config.username" maxlength="100" show-word-limit/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.password')" prop="password">
<el-input v-model="config.password" maxlength="30" show-word-limit show-password
autocomplete="new-password"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
import {TCPConfig} from "../../../model/ApiTestModel";
export default {
name: "MsTcpConfig",
props: {
config: TCPConfig,
isReadOnly: {
type: Boolean,
default: false
},
},
data() {
return {
classes: TCPConfig.CLASSES,
rules: {}
}
},
}
</script>
<style scoped>
.tcp >>> .el-input-number {
width: 100%;
}
</style>
......@@ -86,14 +86,13 @@
</template>
<script>
import MsApiRequestForm from "../request/ApiRequestForm";
import MsApiRequestForm from "../request/http/ApiRequestForm";
import {downloadFile, getUUID} from "@/common/js/utils";
import MsApiCaseList from "../ApiCaseList";
import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer";
import {RequestFactory, Scenario} from "../../model/ApiTestModel";
import {parseEnvironment} from "../../model/EnvironmentModel";
import ApiEnvironmentConfig from "../../../test/components/ApiEnvironmentConfig";
import ApiEnvironmentConfig from "../environment/ApiEnvironmentConfig";
import MsRequestResultTail from "../response/RequestResultTail";
import MsRun from "../Run";
......
......@@ -525,6 +525,7 @@ export default {
pre_script: "Prescript",
post_script: "Postscript",
extract_param: "Extract parameters",
add_module: "Add module",
}
},
......
......@@ -525,7 +525,9 @@ export default {
auth_config_info: "请求需要进行权限校验",
pre_script: "前置脚本",
post_script: "后置脚本",
extract_param:"提取参数",
extract_param: "提取参数",
add_module: "创建模块",
}
},
environment: {
......@@ -704,7 +706,7 @@ export default {
close_connection: "关闭连接",
so_linger: "SO LINGER",
eol_byte: "行尾(EOL)字节值",
request: "要发送的文本",
request: "发送文本",
username: "用户名",
password: "密码",
login: "登录设置",
......
......@@ -526,7 +526,7 @@ export default {
pre_script: "前置腳本",
post_script: "後置腳本",
extract_param: "提取參數",
add_module: "創建模塊",
}
},
environment: {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册