feat(接口测试): 环境变量增加导入导出 (#17018)

--story=1008922 --user=王孝刚 【接口测试】环境参数的查询功能,以及场景变量和环境参数的导入导出功能;
https://www.tapd.cn/55049933/s/1220274

Co-authored-by: wxg0103 <727495428@qq.com>
This commit is contained in:
MeterSphere Bot 2022-08-18 10:27:53 +08:00 committed by GitHub
parent 07ea672ff9
commit 81955c649a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 2405 additions and 600 deletions

View File

@ -65,16 +65,18 @@ public class ApiTestEnvironmentController {
@PostMapping("/add") @PostMapping("/add")
@MsAuditLog(module = OperLogModule.PROJECT_ENVIRONMENT_SETTING, type = OperLogConstants.CREATE, content = "#msClass.getLogDetails(#apiTestEnvironmentWithBLOBs.id)", msClass = ApiTestEnvironmentService.class) @MsAuditLog(module = OperLogModule.PROJECT_ENVIRONMENT_SETTING, type = OperLogConstants.CREATE, content = "#msClass.getLogDetails(#apiTestEnvironmentWithBLOBs.id)", msClass = ApiTestEnvironmentService.class)
public String create(@RequestPart("request") ApiTestEnvironmentDTO apiTestEnvironmentWithBLOBs, @RequestPart(value = "files", required = false) List<MultipartFile> sslFiles) { public String create(@RequestPart("request") ApiTestEnvironmentDTO apiTestEnvironmentWithBLOBs, @RequestPart(value = "files", required = false) List<MultipartFile> sslFiles,
@RequestPart(value = "variablesFiles", required = false) List<MultipartFile> variableFile) {
checkParams(apiTestEnvironmentWithBLOBs); checkParams(apiTestEnvironmentWithBLOBs);
return apiTestEnvironmentService.add(apiTestEnvironmentWithBLOBs, sslFiles); return apiTestEnvironmentService.add(apiTestEnvironmentWithBLOBs, sslFiles, variableFile);
} }
@PostMapping(value = "/update") @PostMapping(value = "/update")
@MsAuditLog(module = OperLogModule.PROJECT_ENVIRONMENT_SETTING, type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#apiTestEnvironment.id)", content = "#msClass.getLogDetails(#apiTestEnvironment.id)", msClass = ApiTestEnvironmentService.class) @MsAuditLog(module = OperLogModule.PROJECT_ENVIRONMENT_SETTING, type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#apiTestEnvironment.id)", content = "#msClass.getLogDetails(#apiTestEnvironment.id)", msClass = ApiTestEnvironmentService.class)
public void update(@RequestPart("request") ApiTestEnvironmentDTO apiTestEnvironment, @RequestPart(value = "files", required = false) List<MultipartFile> sslFiles) { public void update(@RequestPart("request") ApiTestEnvironmentDTO apiTestEnvironment, @RequestPart(value = "files", required = false) List<MultipartFile> sslFiles,
@RequestPart(value = "variablesFiles", required = false) List<MultipartFile> variableFile) {
checkParams(apiTestEnvironment); checkParams(apiTestEnvironment);
apiTestEnvironmentService.update(apiTestEnvironment, sslFiles); apiTestEnvironmentService.update(apiTestEnvironment, sslFiles, variableFile);
} }
private void checkParams(ApiTestEnvironmentDTO apiTestEnvironment) { private void checkParams(ApiTestEnvironmentDTO apiTestEnvironment) {

View File

@ -8,4 +8,5 @@ import java.util.List;
@Data @Data
public class ApiTestEnvironmentDTO extends ApiTestEnvironmentWithBLOBs { public class ApiTestEnvironmentDTO extends ApiTestEnvironmentWithBLOBs {
private List<String> uploadIds; private List<String> uploadIds;
private List<String> variablesFilesIds;
} }

View File

@ -11,7 +11,6 @@ import io.metersphere.api.dto.EnvironmentType;
import io.metersphere.api.dto.definition.request.controller.MsLoopController; import io.metersphere.api.dto.definition.request.controller.MsLoopController;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable; import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig; import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.scenario.request.BodyFile; import io.metersphere.api.dto.scenario.request.BodyFile;
import io.metersphere.api.service.ApiTestEnvironmentService; import io.metersphere.api.service.ApiTestEnvironmentService;
@ -69,7 +68,7 @@ public class ElementUtil {
arguments.setName(StringUtils.isNoneBlank(name) ? name : "Arguments"); arguments.setName(StringUtils.isNoneBlank(name) ? name : "Arguments");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName()); arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel")); arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
config.getConfig().get(projectId).getCommonConfig().getVariables().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue -> config.getConfig().get(projectId).getCommonConfig().getVariables().stream().filter(ScenarioVariable::isConstantValid).filter(ScenarioVariable::isEnable).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=") arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
); );
if (arguments.getArguments().size() > 0) { if (arguments.getArguments().size() > 0) {
@ -101,9 +100,9 @@ public class ElementUtil {
public static void addCsvDataSet(HashTree tree, List<ScenarioVariable> variables, ParameterConfig config, String shareMode) { public static void addCsvDataSet(HashTree tree, List<ScenarioVariable> variables, ParameterConfig config, String shareMode) {
if (CollectionUtils.isNotEmpty(variables)) { if (CollectionUtils.isNotEmpty(variables)) {
List<ScenarioVariable> list = variables.stream().filter(ScenarioVariable::isCSVValid).collect(Collectors.toList()); List<ScenarioVariable> list = variables.stream().filter(ScenarioVariable::isCSVValid).filter(ScenarioVariable::isEnable).collect(Collectors.toList());
if (CollectionUtils.isEmpty(list) && CollectionUtils.isNotEmpty(config.getTransferVariables())) { if (CollectionUtils.isEmpty(list) && CollectionUtils.isNotEmpty(config.getTransferVariables())) {
list = config.getTransferVariables().stream().filter(ScenarioVariable::isCSVValid).collect(Collectors.toList()); list = config.getTransferVariables().stream().filter(ScenarioVariable::isCSVValid).filter(ScenarioVariable::isEnable).collect(Collectors.toList());
} }
if (CollectionUtils.isNotEmpty(list)) { if (CollectionUtils.isNotEmpty(list)) {
list.forEach(item -> { list.forEach(item -> {
@ -140,7 +139,7 @@ public class ElementUtil {
public static void addCounter(HashTree tree, List<ScenarioVariable> variables, boolean isInternal) { public static void addCounter(HashTree tree, List<ScenarioVariable> variables, boolean isInternal) {
if (CollectionUtils.isNotEmpty(variables)) { if (CollectionUtils.isNotEmpty(variables)) {
List<ScenarioVariable> list = variables.stream().filter(ScenarioVariable::isCounterValid).collect(Collectors.toList()); List<ScenarioVariable> list = variables.stream().filter(ScenarioVariable::isCounterValid).filter(ScenarioVariable::isEnable).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(list)) { if (CollectionUtils.isNotEmpty(list)) {
list.forEach(item -> { list.forEach(item -> {
CounterConfig counterConfig = new CounterConfig(); CounterConfig counterConfig = new CounterConfig();
@ -166,7 +165,7 @@ public class ElementUtil {
public static void addRandom(HashTree tree, List<ScenarioVariable> variables) { public static void addRandom(HashTree tree, List<ScenarioVariable> variables) {
if (CollectionUtils.isNotEmpty(variables)) { if (CollectionUtils.isNotEmpty(variables)) {
List<ScenarioVariable> list = variables.stream().filter(ScenarioVariable::isRandom).collect(Collectors.toList()); List<ScenarioVariable> list = variables.stream().filter(ScenarioVariable::isRandom).filter(ScenarioVariable::isEnable).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(list)) { if (CollectionUtils.isNotEmpty(list)) {
list.forEach(item -> { list.forEach(item -> {
RandomVariableConfig randomVariableConfig = new RandomVariableConfig(); RandomVariableConfig randomVariableConfig = new RandomVariableConfig();
@ -794,4 +793,62 @@ public class ElementUtil {
return evlValue; return evlValue;
} }
} }
public static Arguments getConfigArguments(ParameterConfig config, String name, String projectId, List<ScenarioVariable> variables) {
Arguments arguments = new Arguments();
arguments.setEnabled(true);
arguments.setName(StringUtils.isNotEmpty(name) ? name : "Arguments");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
// 场景变量
if (CollectionUtils.isNotEmpty(variables)) {
variables.stream().filter(ScenarioVariable::isConstantValid).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
List<ScenarioVariable> variableList = variables.stream().filter(ScenarioVariable::isListValid).collect(Collectors.toList());
variableList.forEach(item -> {
String[] arrays = item.getValue().split(",");
for (int i = 0; i < arrays.length; i++) {
arguments.addArgument(item.getName() + "_" + (i + 1), arrays[i], "=");
}
});
}
// 环境通用变量
if (config.isEffective(projectId) && config.getConfig().get(projectId).getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().get(projectId).getCommonConfig().getVariables())) {
//常量
List<ScenarioVariable> constants = config.getConfig().get(projectId).getCommonConfig().getVariables().stream().filter(ScenarioVariable::isConstantValid).filter(ScenarioVariable::isEnable).collect(Collectors.toList());
constants.forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
// List类型的变量
List<ScenarioVariable> variableList = config.getConfig().get(projectId).getCommonConfig().getVariables().stream().filter(ScenarioVariable::isListValid).filter(ScenarioVariable::isEnable).collect(Collectors.toList());
variableList.forEach(item -> {
String[] arrays = item.getValue().split(",");
for (int i = 0; i < arrays.length; i++) {
arguments.addArgument(item.getName() + "_" + (i + 1), arrays[i], "=");
}
});
// 清空变量防止重复添加
config.getConfig().get(projectId).getCommonConfig().getVariables().remove(constants);
config.getConfig().get(projectId).getCommonConfig().getVariables().remove(variableList);
}
if (arguments.getArguments() != null && arguments.getArguments().size() > 0) {
return arguments;
}
return null;
}
public static void addOtherVariables(ParameterConfig config, HashTree httpSamplerTree, String projectId) {
if (config.isEffective(projectId) && config.getConfig().get(projectId).getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().get(projectId).getCommonConfig().getVariables())) {
ElementUtil.addCsvDataSet(httpSamplerTree, config.getConfig().get(projectId).getCommonConfig().getVariables(), config, "shareMode.group");
ElementUtil.addCounter(httpSamplerTree, config.getConfig().get(projectId).getCommonConfig().getVariables(), false);
ElementUtil.addRandom(httpSamplerTree, config.getConfig().get(projectId).getCommonConfig().getVariables());
}
}
} }

View File

@ -41,7 +41,6 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -127,7 +126,7 @@ public class MsScenario extends MsTestElement {
} }
} }
} }
if (CollectionUtils.isNotEmpty(this.getVariables()) && (this.variableEnable == null || this.variableEnable)) { if (CollectionUtils.isNotEmpty(this.getVariables()) && (this.variableEnable == null || this.variableEnable)) {
config.setVariables(this.variables); config.setVariables(this.variables);
} }
HashTree scenarioTree = tree; HashTree scenarioTree = tree;
@ -145,7 +144,8 @@ public class MsScenario extends MsTestElement {
scenarioTree = MsCriticalSectionController.createHashTree(tree, this.getName(), this.isEnable()); scenarioTree = MsCriticalSectionController.createHashTree(tree, this.getName(), this.isEnable());
} }
// 环境变量 // 环境变量
Arguments arguments = arguments(this.isEnvironmentEnable() ? newConfig : config); Arguments arguments = ElementUtil.getConfigArguments(this.isEnvironmentEnable() ? newConfig : config, this.getName(), this.getProjectId(), this.getVariables());
if (arguments != null && (this.variableEnable == null || this.variableEnable)) { if (arguments != null && (this.variableEnable == null || this.variableEnable)) {
Arguments valueSupposeMock = ParameterConfig.valueSupposeMock(arguments); Arguments valueSupposeMock = ParameterConfig.valueSupposeMock(arguments);
// 这里加入自定义变量解决ForEach循环控制器取值问题循环控制器无法从vars中取值 // 这里加入自定义变量解决ForEach循环控制器取值问题循环控制器无法从vars中取值
@ -309,41 +309,6 @@ public class MsScenario extends MsTestElement {
} }
} }
private Arguments arguments(ParameterConfig config) {
Arguments arguments = new Arguments();
arguments.setEnabled(true);
arguments.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : "Arguments");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
// 场景变量
if (CollectionUtils.isNotEmpty(this.getVariables())) {
this.getVariables().stream().filter(ScenarioVariable::isConstantValid).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
List<ScenarioVariable> variableList = this.getVariables().stream().filter(ScenarioVariable::isListValid).collect(Collectors.toList());
variableList.forEach(item -> {
String[] arrays = item.getValue().split(",");
for (int i = 0; i < arrays.length; i++) {
arguments.addArgument(item.getName() + "_" + (i + 1), arrays[i], "=");
}
});
}
// 环境通用变量
if (config.isEffective(this.getProjectId()) && config.getConfig().get(this.getProjectId()).getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables())) {
config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
// 清空变量防止重复添加
config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables().clear();
}
if (arguments.getArguments() != null && arguments.getArguments().size() > 0) {
return arguments;
}
return null;
}
private void setEnv(Map<String, String> environmentMap, Map<String, EnvironmentConfig> envConfig) { private void setEnv(Map<String, String> environmentMap, Map<String, EnvironmentConfig> envConfig) {
for (String projectId : environmentMap.keySet()) { for (String projectId : environmentMap.keySet()) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class); ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);

View File

@ -2,8 +2,8 @@ package io.metersphere.api.dto.definition.request.dns;
import com.alibaba.fastjson.annotation.JSONType; import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.ParameterConfig; import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.dto.scenario.HttpConfig; import io.metersphere.api.dto.scenario.HttpConfig;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig; import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.scenario.environment.Host; import io.metersphere.api.dto.scenario.environment.Host;
import io.metersphere.plugin.core.MsParameter; import io.metersphere.plugin.core.MsParameter;
@ -65,14 +65,14 @@ public class MsDNSCacheManager extends MsTestElement {
} }
} }
private static Arguments arguments(String name, List<KeyValue> variables) { private static Arguments arguments(String name, List<ScenarioVariable> variables) {
Arguments arguments = new Arguments(); Arguments arguments = new Arguments();
arguments.setEnabled(true); arguments.setEnabled(true);
arguments.setName(name); arguments.setName(name);
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName()); arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel")); arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
variables.stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue -> variables.stream().filter(ScenarioVariable::isConstantValid).filter(ScenarioVariable::isEnable).forEach(ScenarioVariable ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=") arguments.addArgument(ScenarioVariable.getName(), ScenarioVariable.getValue(), "=")
); );
return arguments; return arguments;
} }

View File

@ -226,10 +226,12 @@ public class MsHTTPSamplerProxy extends MsTestElement {
setHeader(httpSamplerTree, config.getHeaders()); setHeader(httpSamplerTree, config.getHeaders());
} }
// 环境通用请求头 // 环境通用请求头
Arguments arguments = getConfigArguments(config); Arguments arguments = ElementUtil.getConfigArguments(config, this.getName(), this.getProjectId(), null);
if (arguments != null) { if (arguments != null) {
httpSamplerTree.add(arguments); httpSamplerTree.add(arguments);
} }
//添加csv
ElementUtil.addOtherVariables(config, httpSamplerTree, this.getProjectId());
//判断是否要开启DNS //判断是否要开启DNS
if (config.isEffective(this.getProjectId()) && config.getConfig().get(this.getProjectId()).getCommonConfig() != null if (config.isEffective(this.getProjectId()) && config.getConfig().get(this.getProjectId()).getCommonConfig() != null
&& config.getConfig().get(this.getProjectId()).getCommonConfig().isEnableHost()) { && config.getConfig().get(this.getProjectId()).getCommonConfig().isEnableHost()) {
@ -353,6 +355,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
} }
} }
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(e.getMessage(), e);
} }
} }
if (StringUtils.isNotEmpty(useEvnId) && !StringUtils.equals(useEvnId, this.getEnvironmentId())) { if (StringUtils.isNotEmpty(useEvnId) && !StringUtils.equals(useEvnId, this.getEnvironmentId())) {
@ -682,7 +685,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
arguments.addArgument(httpArgument); arguments.addArgument(httpArgument);
} }
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(e.getMessage(), e);
} }
} }
); );
@ -728,30 +731,6 @@ public class MsHTTPSamplerProxy extends MsTestElement {
} }
} }
/**
* 环境通用变量这里只适用用接口定义和用例场景自动化会加到场景中
*/
private Arguments getConfigArguments(ParameterConfig config) {
Arguments arguments = new Arguments();
arguments.setEnabled(true);
arguments.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : "Arguments");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
// 环境通用变量
if (config.isEffective(this.getProjectId()) && config.getConfig().get(this.getProjectId()).getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables())) {
config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
// 清空变量防止重复添加
config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables().clear();
}
if (arguments.getArguments() != null && arguments.getArguments().size() > 0) {
return arguments;
}
return null;
}
private void addArguments(HashTree tree, String key, String value) { private void addArguments(HashTree tree, String key, String value) {
Arguments arguments = new Arguments(); Arguments arguments = new Arguments();
arguments.setEnabled(true); arguments.setEnabled(true);

View File

@ -165,11 +165,12 @@ public class MsJDBCSampler extends MsTestElement {
tree.add(arguments); tree.add(arguments);
} }
// 环境通用请求头 // 环境通用请求头
Arguments envArguments = getConfigArguments(config); Arguments envArguments = ElementUtil.getConfigArguments(config, this.getName(), this.getProjectId(), null);
if (envArguments != null) { if (envArguments != null) {
tree.add(envArguments); tree.add(envArguments);
} }
//添加csv
ElementUtil.addOtherVariables(config, tree, this.getProjectId());
//增加误报全局断言 //增加误报全局断言
HashTreeUtil.addPositive(envConfig, samplerHashTree, config, this.getProjectId()); HashTreeUtil.addPositive(envConfig, samplerHashTree, config, this.getProjectId());
@ -192,30 +193,6 @@ public class MsJDBCSampler extends MsTestElement {
} }
/**
* 环境通用变量
*/
private Arguments getConfigArguments(ParameterConfig config) {
Arguments arguments = new Arguments();
arguments.setEnabled(true);
arguments.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : "Arguments");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
// 环境通用变量
if (config.isEffective(this.getProjectId()) && config.getConfig().get(this.getProjectId()).getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables())) {
config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), ElementUtil.getEvlValue(keyValue.getValue()), "=")
);
// 清空变量防止重复添加
config.getConfig().get(this.getProjectId()).getCommonConfig().getVariables().clear();
}
if (arguments.getArguments() != null && arguments.getArguments().size() > 0) {
return arguments;
}
return null;
}
private boolean isDataSource(List<DatabaseConfig> databaseConfigs) { private boolean isDataSource(List<DatabaseConfig> databaseConfigs) {
List<String> ids = databaseConfigs.stream().map(DatabaseConfig::getId).collect(Collectors.toList()); List<String> ids = databaseConfigs.stream().map(DatabaseConfig::getId).collect(Collectors.toList());
if (StringUtils.isNotEmpty(this.dataSourceId) && ids.contains(this.dataSourceId)) { if (StringUtils.isNotEmpty(this.dataSourceId) && ids.contains(this.dataSourceId)) {

View File

@ -171,11 +171,12 @@ public class MsTCPSampler extends MsTestElement {
} }
// 添加环境中的公共变量 // 添加环境中的公共变量
Arguments arguments = ElementUtil.addArguments(config, this.getProjectId(), this.getName()); Arguments arguments = ElementUtil.getConfigArguments(config, this.getName(), this.getProjectId(), null);
if (arguments != null) { if (arguments != null) {
tree.add(arguments); tree.add(arguments);
} }
//添加csv
ElementUtil.addOtherVariables(config, tree, this.getProjectId());
final HashTree samplerHashTree = new ListedHashTree(); final HashTree samplerHashTree = new ListedHashTree();
samplerHashTree.add(tcpConfig()); samplerHashTree.add(tcpConfig());
tree.set(tcpSampler(config), samplerHashTree); tree.set(tcpSampler(config), samplerHashTree);

View File

@ -13,7 +13,7 @@ public class ScenarioVariable {
/** /**
* CONSTANT LIST CSV COUNTER RANDOM * CONSTANT LIST CSV COUNTER RANDOM
*/ */
private String type; private String type = VariableTypeConstants.CONSTANT.name();
private String id; private String id;
private String name; private String name;
@ -41,6 +41,8 @@ public class ScenarioVariable {
private String minNumber; private String minNumber;
private String maxNumber; private String maxNumber;
private boolean enable = true;
public ScenarioVariable() { public ScenarioVariable() {
} }

View File

@ -1,13 +1,13 @@
package io.metersphere.api.dto.scenario.environment; package io.metersphere.api.dto.scenario.environment;
import io.metersphere.api.dto.scenario.KeyValue; import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
@Data @Data
public class CommonConfig { public class CommonConfig {
private List<KeyValue> variables; private List<ScenarioVariable> variables;
private boolean enableHost; private boolean enableHost;
private List<Host> hosts; private List<Host> hosts;
private int requestTimeout; private int requestTimeout;

View File

@ -8,9 +8,12 @@ import io.metersphere.api.dto.mockconfig.MockConfigStaticData;
import io.metersphere.api.tcp.TCPPool; import io.metersphere.api.tcp.TCPPool;
import io.metersphere.base.domain.ApiTestEnvironmentExample; import io.metersphere.base.domain.ApiTestEnvironmentExample;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs; import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.base.domain.FileAssociationExample;
import io.metersphere.base.domain.Project; import io.metersphere.base.domain.Project;
import io.metersphere.base.mapper.ApiTestEnvironmentMapper; import io.metersphere.base.mapper.ApiTestEnvironmentMapper;
import io.metersphere.base.mapper.FileAssociationMapper;
import io.metersphere.base.mapper.ext.ExtApiTestEnvironmentMapper; import io.metersphere.base.mapper.ext.ExtApiTestEnvironmentMapper;
import io.metersphere.commons.constants.FileAssociationType;
import io.metersphere.commons.constants.ProjectApplicationType; import io.metersphere.commons.constants.ProjectApplicationType;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
@ -25,6 +28,7 @@ import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn; import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails; import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference; import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.metadata.service.FileAssociationService;
import io.metersphere.service.EnvironmentGroupProjectService; import io.metersphere.service.EnvironmentGroupProjectService;
import io.metersphere.service.ProjectApplicationService; import io.metersphere.service.ProjectApplicationService;
import io.metersphere.service.ProjectService; import io.metersphere.service.ProjectService;
@ -50,6 +54,10 @@ public class ApiTestEnvironmentService {
private ProjectApplicationService projectApplicationService; private ProjectApplicationService projectApplicationService;
@Resource @Resource
private ExtApiTestEnvironmentMapper extApiTestEnvironmentMapper; private ExtApiTestEnvironmentMapper extApiTestEnvironmentMapper;
@Resource
private FileAssociationService fileAssociationService;
@Resource
private FileAssociationMapper fileAssociationMapper;
public List<ApiTestEnvironmentWithBLOBs> list(String projectId) { public List<ApiTestEnvironmentWithBLOBs> list(String projectId) {
ApiTestEnvironmentExample example = new ApiTestEnvironmentExample(); ApiTestEnvironmentExample example = new ApiTestEnvironmentExample();
@ -85,6 +93,9 @@ public class ApiTestEnvironmentService {
public void delete(String id) { public void delete(String id) {
apiTestEnvironmentMapper.deleteByPrimaryKey(id); apiTestEnvironmentMapper.deleteByPrimaryKey(id);
environmentGroupProjectService.deleteRelateEnv(id); environmentGroupProjectService.deleteRelateEnv(id);
FileAssociationExample associationExample = new FileAssociationExample();
associationExample.createCriteria().andSourceIdEqualTo(id);
fileAssociationMapper.deleteByExample(associationExample);
} }
public void update(ApiTestEnvironmentWithBLOBs apiTestEnvironment) { public void update(ApiTestEnvironmentWithBLOBs apiTestEnvironment) {
@ -99,16 +110,19 @@ public class ApiTestEnvironmentService {
return apiTestEnvironmentWithBLOBs.getId(); return apiTestEnvironmentWithBLOBs.getId();
} }
public String add(ApiTestEnvironmentDTO request, List<MultipartFile> sslFiles) { public String add(ApiTestEnvironmentDTO request, List<MultipartFile> sslFiles, List<MultipartFile> variableFile) {
request.setId(UUID.randomUUID().toString()); request.setId(UUID.randomUUID().toString());
request.setCreateUser(SessionUtils.getUserId()); request.setCreateUser(SessionUtils.getUserId());
checkEnvironmentExist(request); checkEnvironmentExist(request);
FileUtils.createFiles(request.getUploadIds(), sslFiles, FileUtils.BODY_FILE_DIR + "/ssl"); FileUtils.createFiles(request.getUploadIds(), sslFiles, FileUtils.BODY_FILE_DIR + "/ssl");
FileUtils.createBodyFiles(request.getVariablesFilesIds(), variableFile);
//检查Config判断isMock参数是否给True //检查Config判断isMock参数是否给True
request = this.updateConfig(request, false); request = this.updateConfig(request, false);
request.setCreateTime(System.currentTimeMillis()); request.setCreateTime(System.currentTimeMillis());
request.setUpdateTime(System.currentTimeMillis()); request.setUpdateTime(System.currentTimeMillis());
apiTestEnvironmentMapper.insert(request); apiTestEnvironmentMapper.insert(request);
// 存储附件关系
fileAssociationService.saveEnvironment(request.getId(), request.getConfig(), FileAssociationType.ENVIRONMENT.name());
return request.getId(); return request.getId();
} }
@ -128,10 +142,13 @@ public class ApiTestEnvironmentService {
return request; return request;
} }
public void update(ApiTestEnvironmentDTO apiTestEnvironment, List<MultipartFile> sslFiles) { public void update(ApiTestEnvironmentDTO apiTestEnvironment, List<MultipartFile> sslFiles, List<MultipartFile> variablesFiles) {
checkEnvironmentExist(apiTestEnvironment); checkEnvironmentExist(apiTestEnvironment);
FileUtils.createFiles(apiTestEnvironment.getUploadIds(), sslFiles, FileUtils.BODY_FILE_DIR + "/ssl"); FileUtils.createFiles(apiTestEnvironment.getUploadIds(), sslFiles, FileUtils.BODY_FILE_DIR + "/ssl");
FileUtils.createBodyFiles(apiTestEnvironment.getVariablesFilesIds(), variablesFiles);
apiTestEnvironment.setUpdateTime(System.currentTimeMillis()); apiTestEnvironment.setUpdateTime(System.currentTimeMillis());
// 存储附件关系
fileAssociationService.saveEnvironment(apiTestEnvironment.getId(), apiTestEnvironment.getConfig(), FileAssociationType.ENVIRONMENT.name());
apiTestEnvironmentMapper.updateByPrimaryKeyWithBLOBs(apiTestEnvironment); apiTestEnvironmentMapper.updateByPrimaryKeyWithBLOBs(apiTestEnvironment);
} }

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants; package io.metersphere.commons.constants;
public enum FileAssociationType { public enum FileAssociationType {
API, CASE, SCENARIO, UI API, CASE, SCENARIO, UI, ENVIRONMENT
} }

View File

@ -1,5 +1,6 @@
package io.metersphere.metadata.service; package io.metersphere.metadata.service;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.definition.request.MsScenario; import io.metersphere.api.dto.definition.request.MsScenario;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable; import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
@ -104,7 +105,7 @@ public class FileAssociationService {
} }
} }
public void saveApi(String id, MsTestElement request,String type) { public void saveApi(String id, MsTestElement request, String type) {
this.deleteByResourceId(id); this.deleteByResourceId(id);
if (StringUtils.isNotEmpty(id) && request != null && StringUtils.equalsIgnoreCase(request.getType(), HTTPSamplerProxy.class.getSimpleName())) { if (StringUtils.isNotEmpty(id) && request != null && StringUtils.equalsIgnoreCase(request.getType(), HTTPSamplerProxy.class.getSimpleName())) {
MsHTTPSamplerProxy samplerProxy = (MsHTTPSamplerProxy) request; MsHTTPSamplerProxy samplerProxy = (MsHTTPSamplerProxy) request;
@ -134,6 +135,22 @@ public class FileAssociationService {
} }
} }
public void saveEnvironment(String id, String config, String type) {
this.deleteByResourceId(id);
List<BodyFile> files = new ArrayList<>();
if (StringUtils.isNotEmpty(config)) {
JSONObject commonConfig = JSONObject.parseObject(config).getJSONObject("commonConfig");
List<ScenarioVariable> list = JSONObject.parseArray(commonConfig.getString("variables"), ScenarioVariable.class);
list.stream().filter(ScenarioVariable::isCSVValid).forEach(keyValue -> {
files.addAll(keyValue.getFiles().stream().filter(BodyFile::isRef).collect(Collectors.toList()));
});
}
if (!CollectionUtils.isEmpty(files)) {
List<BodyFile> list = files.stream().distinct().collect(Collectors.toList());
this.save(list, type, id);
}
}
private void getHashTree(List<MsTestElement> testElements, List<BodyFile> files) { private void getHashTree(List<MsTestElement> testElements, List<BodyFile> files) {
testElements.forEach(item -> { testElements.forEach(item -> {
if (StringUtils.equalsIgnoreCase(item.getType(), HTTPSamplerProxy.class.getSimpleName())) { if (StringUtils.equalsIgnoreCase(item.getType(), HTTPSamplerProxy.class.getSimpleName())) {

View File

@ -129,11 +129,11 @@ export default {
}, },
download() { download() {
// //
if (this.parameter.files && this.parameter.files.length > 0 && this.parameter.files[0].file) { if (this.parameter.files && this.parameter.files.length > 0 && this.parameter.files[0].file && this.parameter.files[0].file.name) {
downloadFile(this.parameter.files[0].file.name, this.parameter.files[0].file); downloadFile(this.parameter.files[0].file.name, this.parameter.files[0].file);
} }
// //
if (this.parameter.files && this.parameter.files.length > 0 && !this.parameter.files[0].file) { if (this.parameter.files && this.parameter.files.length > 0 && (!this.parameter.files[0].file || !this.parameter.files[0].file.name)) {
let file = this.parameter.files[0]; let file = this.parameter.files[0];
let conf = { let conf = {
url: "/api/automation/file/download", url: "/api/automation/file/download",
@ -157,15 +157,15 @@ export default {
} }
}, },
handleRemove(file) { handleRemove(file) {
let fileName = file.file ? file.file.name : file.name let fileName = file.name ? file.name : file.file.name
this.$alert('是否确认删除CSV文件:【 ' + fileName + " 】?", '', { this.$alert(this.$t('api_test.environment.csv_delete') + ':【 ' + fileName + " 】?", '', {
confirmButtonText: this.$t('commons.confirm'), confirmButtonText: this.$t('commons.confirm'),
callback: (action) => { callback: (action) => {
if (action === 'confirm') { if (action === 'confirm') {
this.$refs.upload.handleRemove(file); this.$refs.upload.handleRemove(file);
for (let i = 0; i < this.parameter.files.length; i++) { for (let i = 0; i < this.parameter.files.length; i++) {
let paramFileName = this.parameter.files[i].file ? let paramFileName = this.parameter.files[i].name ?
this.parameter.files[i].file.name : this.parameter.files[i].name; this.parameter.files[i].name : this.parameter.files[i].file.name;
if (fileName === paramFileName) { if (fileName === paramFileName) {
this.parameter.files.splice(i, 1); this.parameter.files.splice(i, 1);
this.$refs.upload.handleRemove(file); this.$refs.upload.handleRemove(file);
@ -180,6 +180,7 @@ export default {
this.$warning(this.$t('test_track.case.import.upload_limit_count')); this.$warning(this.$t('test_track.case.import.upload_limit_count'));
}, },
upload(file) { upload(file) {
this.parameter.files = [];
this.parameter.files.push(file); this.parameter.files.push(file);
}, },
uploadValidate(file) { uploadValidate(file) {

View File

@ -17,7 +17,7 @@
<el-tab-pane :label="$t('variables.config')" name="config"> <el-tab-pane :label="$t('variables.config')" name="config">
<el-row> <el-row>
<el-col :span="5" style="margin-top: 5px"> <el-col :span="5" style="margin-top: 5px">
<span>{{$t('variables.add_file')}}</span> <span>{{ $t('variables.add_file') }}</span>
</el-col> </el-col>
<el-col :span="19"> <el-col :span="19">
<ms-csv-file-upload :parameter="editData"/> <ms-csv-file-upload :parameter="editData"/>
@ -40,7 +40,7 @@
</el-row> </el-row>
<el-row style="margin-top: 10px"> <el-row style="margin-top: 10px">
<el-col :span="5" style="margin-top: 5px"> <el-col :span="5" style="margin-top: 5px">
<span>{{$t('variables.delimiter')}}</span> <span>{{ $t('variables.delimiter') }}</span>
</el-col> </el-col>
<el-col :span="19"> <el-col :span="19">
<el-input v-model="editData.delimiter" size="small" :disabled="disabled"/> <el-input v-model="editData.delimiter" size="small" :disabled="disabled"/>
@ -48,7 +48,7 @@
</el-row> </el-row>
<el-row style="margin-top: 10px"> <el-row style="margin-top: 10px">
<el-col :span="5" style="margin-top: 5px"> <el-col :span="5" style="margin-top: 5px">
<span>{{$t('variables.quoted_data')}}</span> <span>{{ $t('variables.quoted_data') }}</span>
</el-col> </el-col>
<el-col :span="19"> <el-col :span="19">
<el-select v-model="editData.quotedData" size="small" :disabled="disabled"> <el-select v-model="editData.quotedData" size="small" :disabled="disabled">
@ -66,10 +66,11 @@
height="200px" height="200px"
v-loading="loading"> v-loading="loading">
<!-- 自定义列的遍历--> <!-- 自定义列的遍历-->
<el-table-column v-for="(item, index) in columns" :key="index" :label="columns[index]" align="left" width="180"> <el-table-column v-for="(item, index) in columns" :key="index" :label="columns[index]" align="left"
width="180">
<!-- 数据的遍历 scope.row就代表数据的每一个对象--> <!-- 数据的遍历 scope.row就代表数据的每一个对象-->
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{scope.row[index]}}</span> <span>{{ scope.row[index] }}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -79,70 +80,70 @@
</template> </template>
<script> <script>
import MsCsvFileUpload from "./CsvFileUpload"; import MsCsvFileUpload from "./CsvFileUpload";
export default { export default {
name: "MsEditCsv", name: "MsEditCsv",
components: { components: {
MsCsvFileUpload MsCsvFileUpload
},
props: {
editData: {},
},
data() {
return {
activeName: "config",
visible: false,
loading: false,
editFlag: false,
previewData: [],
columns: [],
allData: [],
showMessage: false,
rules: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
],
},
}
},
computed: {
disabled() {
return !(this.editData.name && this.editData.name !== "");
}
},
watch: {
'editData.name': {
handler(v) {
this.handleClick();
}
}
},
methods: {
complete(results) {
if (results.errors && results.errors.length > 0) {
this.$error(results.errors);
return;
}
if (this.allData) {
this.columns = this.allData[0];
this.allData.splice(0, 1);
this.previewData = this.allData;
}
this.loading = false;
}, },
props: { cleanPreview() {
editData: {}, this.allData = [];
this.columns = [];
this.previewData = [];
}, },
data() { step(results, parser) {
return { if (this.allData.length < 500) {
activeName: "config", this.allData.push(results.data);
visible: false, } else {
loading: false, this.showMessage = true;
editFlag: false,
previewData: [],
columns: [],
allData: [],
showMessage: false,
rules: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
],
},
} }
}, },
computed: {
disabled() {
return !(this.editData.name && this.editData.name !== "");
}
},
watch: {
'editData.name': {
handler(v) {
this.handleClick();
}
}
},
methods: {
complete(results) {
if (results.errors && results.errors.length > 0) {
this.$error(results.errors);
return;
}
if (this.allData) {
this.columns = this.allData[0];
this.allData.splice(0, 1);
this.previewData = this.allData;
}
this.loading = false;
},
cleanPreview() {
this.allData = [];
this.columns = [];
this.previewData = [];
},
step(results, parser) {
if (this.allData.length < 500) {
this.allData.push(results.data);
} else {
this.showMessage = true;
}
},
handleClick() { handleClick() {
let config = { let config = {
@ -152,12 +153,12 @@
}; };
this.allData = []; this.allData = [];
// //
if (this.editData.files && this.editData.files.length > 0 && this.editData.files[0].file) { if (this.editData.files && this.editData.files.length > 0 && this.editData.files[0].file && this.editData.files[0].file.name) {
this.loading = true; this.loading = true;
this.$papa.parse(this.editData.files[0].file, config); this.$papa.parse(this.editData.files[0].file, config);
} }
// //
if (this.editData.files && this.editData.files.length > 0 && !this.editData.files[0].file) { if (this.editData.files && this.editData.files.length > 0 && (!this.editData.files[0].file || !this.editData.files[0].file.name)) {
let file = this.editData.files[0]; let file = this.editData.files[0];
let conf = { let conf = {
url: "/api/automation/file/download", url: "/api/automation/file/download",
@ -186,19 +187,19 @@
}; };
}, },
querySearch(queryString, cb) { querySearch(queryString, cb) {
let restaurants = [{value: "UTF-8"}, {value: "UTF-16"},{value: "GB2312"}, {value: "ISO-8859-15"}, {value: "US-ASCll"}]; let restaurants = [{value: "UTF-8"}, {value: "UTF-16"}, {value: "GB2312"}, {value: "ISO-8859-15"}, {value: "US-ASCll"}];
let results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants; let results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants;
// callback // callback
cb(results); cb(results);
}, },
}
} }
}
</script> </script>
<style scoped> <style scoped>
ms-is-leaf >>> .is-leaf { ms-is-leaf >>> .is-leaf {
color: red; color: red;
} }
</style> </style>

View File

@ -0,0 +1,160 @@
<template>
<el-dialog :visible="dialogVisible" :title="dialogTitle"
@close="close" :close-on-click-modal="false" append-to-body
width="35%">
<el-form :rules="rules" label-width="80px" v-model="modeId">
<el-form-item prop="modeId" :label="$t('commons.import_mode')">
<el-select size="small" v-model="modeId">
<el-option v-for="item in modeOptions" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item>
<el-upload
class="api-upload" drag action="alert"
:on-change="handleFileChange"
:limit="1" :file-list="uploadFiles"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:auto-upload="false" accept=".json">
<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') }}
{{ '' + $t('api_test.api_import.ms_env_import_file_limit') }}
</div>
</el-upload>
</el-form-item>
</el-form>
<template v-slot:footer>
<el-button type="primary" @click="save">
{{ $t('commons.confirm') }}
</el-button>
</template>
</el-dialog>
</template>
<script>
export default {
name: "VariableImport",
props: {
projectList: {
type: Array,
default() {
return [];
}
},
toImportProjectId: {
type: String,
default() {
return "";
}
}
},
data() {
return {
currentProjectId: '', //id
uploadFiles: [],
dialogTitle: this.$t('commons.import_variable'),
dialogVisible: false,
modeOptions: [
{
id: 'fullCoverage',
name: this.$t('commons.cover')
},
{
id: 'incrementalMerge',
name: this.$t('commons.not_cover')
}
],
modeId: 'fullCoverage',
rules: {
modeId: [
{required: true, message: "", trigger: 'blur'},
],
},
}
},
watch: {
//
dialogVisible(val, oldVal) {
if (oldVal === false) {
this.currentProjectId = '';
this.uploadFiles = [];
}
}
},
methods: {
handleFileChange(file, uploadFiles) {
this.uploadFiles = uploadFiles;
},
save() {
if (this.uploadFiles.length > 0) {
for (let i = 0; i < this.uploadFiles.length; i++) {
this.uploadValidate(this.uploadFiles[i]);
let file = this.uploadFiles[i];
if (!file) {
continue;
}
let reader = new FileReader();
reader.readAsText(file.raw)
reader.onload = (e) => {
let fileString = e.target.result;
let messages = '';
try {
JSON.parse(fileString).map(env => {
if (!env.name) {
messages = this.$t('api_test.automation.variable_warning')
}
})
if (messages !== '') {
this.$warning(messages);
return;
}
this.$emit("mergeData", fileString, this.modeId);
this.dialogVisible = false;
this.$success(this.$t('commons.save_success'));
} catch (exception) {
this.$warning(this.$t('api_test.api_import.ms_env_import_file_limit'));
}
}
}
} else {
this.$warning(this.$t('test_track.case.import.import_file_tips'));
}
},
handleExceed() {
this.$warning(this.$t('api_test.api_import.file_exceed_limit'));
},
handleRemove() {
},
uploadValidate(file) { //.json20M
const extension = file.name.substring(file.name.lastIndexOf('.') + 1);
if (!(extension === 'json')) {
this.$warning(this.$t('api_test.api_import.ms_env_import_file_limit'));
}
if (file.size / 1024 / 1024 > 20) {
this.$warning(this.$t('api_test.api_import.file_size_limit'));
}
},
open() {
this.dialogVisible = true;
},
close() {
this.dialogVisible = false;
}
},
}
</script>
<style scoped>
.project-item {
padding-left: 20px;
padding-right: 20px;
}
</style>

View File

@ -37,9 +37,23 @@
<el-button size="small" style="margin-left: 10px" type="primary" @click="addVariable"> <el-button size="small" style="margin-left: 10px" type="primary" @click="addVariable">
{{ $t('commons.add') }} {{ $t('commons.add') }}
</el-button> </el-button>
<el-link @click="batchAddParameter" type="primary" :disabled="disabled" style="margin-left: 10px"> <el-dropdown style="margin-left: 10px">
{{ $t("commons.batch_add") }} <el-button size="small">
</el-link> <span class="tip-font">{{ $t('commons.more_operator') }}</span>
<i class="el-icon-arrow-down el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native.stop="importVariable" @mergeData="mergeData" :disabled="disabled">
{{ $t("commons.import_variable") }}
</el-dropdown-item>
<el-dropdown-item @click.native.stop="exportVariable" :disabled="disabled">
{{ $t("commons.export_variable") }}
</el-dropdown-item>
<el-dropdown-item @click.native.stop="batchAddParameter" :disabled="disabled">
{{ $t("commons.batch_add") }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div> </div>
</el-row> </el-row>
<el-row> <el-row>
@ -94,6 +108,13 @@
:label="$t('api_test.value')" :label="$t('api_test.value')"
sortable> sortable>
</ms-table-column> </ms-table-column>
<ms-table-column
prop="description"
:field="item"
:fields-width="fieldsWidth"
:label="$t('commons.description')"
sortable>
</ms-table-column>
</span> </span>
</ms-table> </ms-table>
<batch-add-parameter @batchSave="batchSaveParameter" ref="batchAddParameter"/> <batch-add-parameter @batchSave="batchSaveParameter" ref="batchAddParameter"/>
@ -149,6 +170,7 @@
</template> </template>
</el-collapse-transition> </el-collapse-transition>
</fieldset> </fieldset>
<variable-import ref="variableImport"></variable-import>
</el-dialog> </el-dialog>
</template> </template>
@ -161,7 +183,7 @@ import MsEditCounter from "./EditCounter";
import MsEditRandom from "./EditRandom"; import MsEditRandom from "./EditRandom";
import MsEditListValue from "./EditListValue"; import MsEditListValue from "./EditListValue";
import MsEditCsv from "./EditCsv"; import MsEditCsv from "./EditCsv";
import {getUUID} from "@/common/js/utils"; import {downloadFile, getUUID} from "@/common/js/utils";
import MsApiKeyValue from "../../../definition/components/ApiKeyValue"; import MsApiKeyValue from "../../../definition/components/ApiKeyValue";
import BatchAddParameter from "../../../definition/components/basis/BatchAddParameter"; import BatchAddParameter from "../../../definition/components/basis/BatchAddParameter";
import {KeyValue} from "../../../definition/model/ApiTestModel"; import {KeyValue} from "../../../definition/model/ApiTestModel";
@ -170,6 +192,7 @@ import {REQUEST_HEADERS} from "@/common/js/constants";
import MsTable from "@/business/components/common/components/table/MsTable"; import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/MsTableColumn"; import MsTableColumn from "@/business/components/common/components/table/MsTableColumn";
import {getCustomTableHeader, getCustomTableWidth} from "@/common/js/tableUtils"; import {getCustomTableHeader, getCustomTableWidth} from "@/common/js/tableUtils";
import VariableImport from "@/business/components/api/automation/scenario/variable/VariableImport";
const jsondiffpatch = require('jsondiffpatch'); const jsondiffpatch = require('jsondiffpatch');
@ -188,6 +211,7 @@ export default {
BatchAddParameter, BatchAddParameter,
MsTableColumn, MsTableColumn,
MsTable, MsTable,
VariableImport
}, },
data() { data() {
return { return {
@ -230,6 +254,46 @@ export default {
}; };
}, },
methods: { methods: {
importVariable() {
this.$refs.variableImport.open();
},
mergeData(data, modeId) {
JSON.parse(data).map(importData => {
importData.id = getUUID();
importData.enable = true;
importData.showMore = false;
let sameNameIndex = this.variables.findIndex(d => d.name === importData.name);
if (sameNameIndex !== -1) {
if (modeId === 'fullCoverage') {
this.variables.splice(sameNameIndex, 1, importData);
}
} else {
this.variables.splice(this.variables.length - 1, 0, importData);
}
})
},
exportVariable() {
if (this.$refs.variableTable.selectIds.length < 1) {
this.$warning(this.$t('api_test.environment.select_environment'));
return;
}
let variablesJson = [];
let messages = '';
let rows = this.$refs.variableTable.selectRows;
rows.forEach(row => {
if (row.type === 'CSV') {
messages = this.$t('variables.csv_download')
}
if (row.name) {
variablesJson.push(row);
}
})
if (messages !== '') {
this.$warning(messages);
return;
}
downloadFile('MS_' + variablesJson.length + '_Environments_variables.json', JSON.stringify(variablesJson));
},
batchAddParameter() { batchAddParameter() {
this.$refs.batchAddParameter.open(); this.$refs.batchAddParameter.open();
}, },
@ -241,20 +305,21 @@ export default {
let params = data.split("\n"); let params = data.split("\n");
let keyValues = []; let keyValues = [];
params.forEach(item => { params.forEach(item => {
let line = item.split(/|:/); if (item) {
let required = false; let line = item.split(/|:/);
keyValues.unshift(new KeyValue({ let required = false;
name: line[0], keyValues.push(new KeyValue({
required: required, name: line[0],
value: line[1], required: required,
description: line[2], value: line[1],
type: "text", description: line[2],
valid: false, type: "CONSTANT",
file: false, valid: false,
encode: true, file: false,
enable: true, encode: true,
contentType: "text/plain" enable: true,
})); }));
}
}) })
return keyValues; return keyValues;
} }
@ -278,7 +343,7 @@ export default {
} }
} }
if (isAdd) { if (isAdd) {
this.headers.unshift(obj); this.headers.splice(this.headers.indexOf(h => !h.name), 0, obj);
} }
} }
}, },
@ -346,6 +411,18 @@ export default {
this.disabled = disabled; this.disabled = disabled;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.variableTable.doLayout(); this.$refs.variableTable.doLayout();
let variable = [];
let messages = '';
this.variables.forEach(item => {
if (variable.indexOf(item.name) !== -1) {
messages += item.name + ' ,';
} else {
variable.push(item.name);
}
})
if (messages !== '') {
this.$alert(this.$t('api_test.scenario.variables') + "【" + messages.substr(0, messages.length - 1) + "】" + this.$t('load_test.param_is_duplicate'));
}
}); });
}, },
save() { save() {
@ -382,6 +459,18 @@ export default {
this.$warning(this.$t('api_test.automation.variable_warning')); this.$warning(this.$t('api_test.automation.variable_warning'));
return; return;
} }
let repeatKey = "";
if (!this.showDelete) {
this.variables.forEach((item, index) => {
if (item.name === this.editData.name) {
repeatKey = item.name;
}
});
}
if (repeatKey !== "") {
this.$warning(this.$t('api_test.scenario.variables') + "【" + repeatKey + "】" + this.$t('load_test.param_is_duplicate'));
return;
}
if (this.editData.type === 'CSV' && this.$refs.csv) { if (this.editData.type === 'CSV' && this.$refs.csv) {
if (this.editData.files.length === 0) { if (this.editData.files.length === 0) {
this.$warning(this.$t('api_test.automation.csv_warning')); this.$warning(this.$t('api_test.automation.csv_warning'));
@ -407,7 +496,7 @@ export default {
deleteVariable() { deleteVariable() {
let ids = [this.editData.id]; let ids = [this.editData.id];
if (ids.length == 0) { if (ids.length == 0) {
this.$warning("请选择一条数据删除"); this.$warning(this.$t('api_test.environment.delete_info'));
return; return;
} }
let message = ""; let message = "";
@ -419,7 +508,7 @@ export default {
}); });
if (message !== "") { if (message !== "") {
message = message.substr(0, message.length - 1); message = message.substr(0, message.length - 1);
this.$alert('是否确认删除变量:【 ' + message + " 】?", '', { this.$alert(this.$t('api_test.environment.variables_delete_info') + ':【 ' + message + " 】?", '', {
confirmButtonText: this.$t('commons.confirm'), confirmButtonText: this.$t('commons.confirm'),
callback: (action) => { callback: (action) => {
if (action === 'confirm') { if (action === 'confirm') {
@ -442,7 +531,7 @@ export default {
} }
}, },
handleDeleteBatch() { handleDeleteBatch() {
this.$alert("是否确认删除所选变量" + ' ' + " ", '', { this.$alert(this.$t('api_test.environment.variables_delete_info') + ' ' + " ", '', {
confirmButtonText: this.$t('commons.confirm'), confirmButtonText: this.$t('commons.confirm'),
callback: (action) => { callback: (action) => {
if (action === 'confirm') { if (action === 'confirm') {

View File

@ -8,12 +8,15 @@
<div v-html="$t('api_test.batch_add_parameter')"/> <div v-html="$t('api_test.batch_add_parameter')"/>
</el-col> </el-col>
<el-col :span="10" class="buttons"> <el-col :span="10" class="buttons">
<el-button size="mini" @click="handleClose">{{$t('commons.cancel')}}</el-button> <el-button size="mini" @click="handleClose">{{ $t('commons.cancel') }}</el-button>
<el-button type="primary" size="mini" @click="confirm" @keydown.enter.native.prevent>{{$t('commons.confirm')}}</el-button> <el-button type="primary" size="mini" @click="confirm" @keydown.enter.native.prevent>
{{ $t('commons.confirm') }}
</el-button>
</el-col> </el-col>
</el-row> </el-row>
<div class="ms-code"> <div class="ms-code">
<ms-code-edit class="ms-code" :enable-format="false" mode="text" :data.sync="parameters" theme="eclipse" :modes="['text']" <ms-code-edit class="ms-code" :enable-format="false" mode="text" :data.sync="parameters" theme="eclipse"
:modes="['text']"
ref="codeEdit"/> ref="codeEdit"/>
</div> </div>
</div> </div>
@ -22,59 +25,75 @@
</template> </template>
<script> <script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter"; import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils"; import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import MsCodeEdit from "../../../../common/components/MsCodeEdit"; import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsDrawer from "../../../../common/components/MsDrawer"; import MsDrawer from "../../../../common/components/MsDrawer";
export default { export default {
name: "BatchAddParameter", name: "BatchAddParameter",
components: { components: {
MsDrawer, MsDrawer,
MsDialogFooter, MsDialogFooter,
MsCodeEdit MsCodeEdit
},
props: {},
data() {
return {
dialogVisible: false,
parameters: "",
}
},
methods: {
open() {
this.dialogVisible = true;
listenGoBack(this.handleClose);
}, },
props: {}, handleClose() {
data() { this.parameters = "";
return { this.dialogVisible = false;
dialogVisible: false, removeGoBackListener(this.handleClose);
parameters: "",
}
}, },
methods: { confirm() {
open() { let params = this.parameters.split("\n");
this.dialogVisible = true; let index = 1;
listenGoBack(this.handleClose); let isNormal = true;
}, params.forEach(item => {
handleClose() { if (item) {
this.parameters = ""; let line = item.split(/|:/);
this.dialogVisible = false; if (!line[0]) {
removeGoBackListener(this.handleClose); isNormal = false;
}, this.$warning(this.$t('api_test.params_format_warning', [index]) + " :" + this.$t('api_test.automation.variable_warning'));
confirm() { return;
}
index++;
}
});
if (isNormal) {
this.dialogVisible = false; this.dialogVisible = false;
this.$emit("batchSave", this.parameters); this.$emit("batchSave", this.parameters);
this.parameters = ""; this.parameters = "";
} }
} }
} }
}
</script> </script>
<style scoped> <style scoped>
.ms-drawer { .ms-drawer {
padding: 10px 13px; padding: 10px 13px;
} }
.ms-code { .ms-code {
height: calc(100vh); height: calc(100vh);
} }
.buttons .el-button { .buttons .el-button {
float: right; float: right;
} }
.buttons .el-button:nth-child(2) { .buttons .el-button:nth-child(2) {
margin-right: 15px; margin-right: 15px;
} }
</style> </style>

View File

@ -302,7 +302,7 @@ export default {
} }
} }
if (isAdd) { if (isAdd) {
this.body.kvs.unshift(obj); this.body.kvs.splice(this.body.kvs.indexOf(kv => !kv.name), 0, obj);
} }
} }
}, },
@ -311,22 +311,24 @@ export default {
let params = data.split("\n"); let params = data.split("\n");
let keyValues = []; let keyValues = [];
params.forEach(item => { params.forEach(item => {
let line = []; if (item) {
line[0] = item.substring(0, item.indexOf(":")); let line = [];
line[1] = item.substring(item.indexOf(":") + 1, item.length); line[0] = item.substring(0, item.indexOf(":"));
let required = false; line[1] = item.substring(item.indexOf(":") + 1, item.length);
keyValues.unshift(new KeyValue({ let required = false;
name: line[0], keyValues.push(new KeyValue({
required: required, name: line[0],
value: line[1], required: required,
description: line[2], value: line[1],
type: "text", description: line[2],
valid: false, type: "text",
file: false, valid: false,
encode: true, file: false,
enable: true, encode: true,
contentType: "text/plain" enable: true,
})); contentType: "text/plain"
}));
}
}) })
keyValues.forEach(item => { keyValues.forEach(item => {
this.format(this.body.kvs, item); this.format(this.body.kvs, item);

View File

@ -2,19 +2,22 @@
<el-main v-loading="result.loading"> <el-main v-loading="result.loading">
<el-form :model="environment" :rules="rules" ref="environment"> <el-form :model="environment" :rules="rules" ref="environment">
<span>{{$t('api_test.environment.name')}}</span> <span>{{ $t('api_test.environment.name') }}</span>
<el-form-item prop="name"> <el-form-item prop="name">
<el-input v-model="environment.name" :disabled="isReadOnly" :placeholder="this.$t('commons.input_name')" clearable/> <el-input v-model="environment.name" :disabled="isReadOnly" :placeholder="this.$t('commons.input_name')"
clearable/>
</el-form-item> </el-form-item>
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.environment.common_config')" name="common"> <el-tab-pane :label="$t('api_test.environment.common_config')" name="common">
<ms-environment-common-config :common-config="environment.config.commonConfig" ref="commonConfig" :is-read-only="isReadOnly"/> <ms-environment-common-config :common-config="environment.config.commonConfig" ref="commonConfig"
:is-read-only="isReadOnly"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.http_config')" name="http"> <el-tab-pane :label="$t('api_test.environment.http_config')" name="http">
<ms-environment-http-config :http-config="environment.config.httpConfig" ref="httpConfig" :is-read-only="isReadOnly"/> <ms-environment-http-config :http-config="environment.config.httpConfig" ref="httpConfig"
:is-read-only="isReadOnly"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.database_config')" name="sql"> <el-tab-pane :label="$t('api_test.environment.database_config')" name="sql">
<ms-database-config :configs="environment.config.databaseConfigs" :is-read-only="isReadOnly"/> <ms-database-config :configs="environment.config.databaseConfigs" :is-read-only="isReadOnly"/>
@ -34,136 +37,237 @@
</template> </template>
<script> <script>
import MsApiScenarioVariables from "../ApiScenarioVariables"; import MsApiScenarioVariables from "../ApiScenarioVariables";
import MsApiKeyValue from "../ApiKeyValue"; import MsApiKeyValue from "../ApiKeyValue";
import MsDialogFooter from "../../../../common/components/MsDialogFooter"; import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {REQUEST_HEADERS} from "@/common/js/constants"; import {REQUEST_HEADERS} from "@/common/js/constants";
import {Environment} from "../../model/EnvironmentModel"; import {Environment} from "../../model/EnvironmentModel";
import MsApiHostTable from "./ApiHostTable"; import MsApiHostTable from "./ApiHostTable";
import MsDatabaseConfig from "../request/database/DatabaseConfig"; import MsDatabaseConfig from "../request/database/DatabaseConfig";
import MsEnvironmentHttpConfig from "../../../test/components/environment/EnvironmentHttpConfig"; import MsEnvironmentHttpConfig from "../../../test/components/environment/EnvironmentHttpConfig";
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig"; import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
import EnvironmentTcpConfig from "./EnvironmentTcpConfig"; import EnvironmentTcpConfig from "./EnvironmentTcpConfig";
import {getUploadConfig, request} from "@/common/js/ajax";
import {getUUID} from "@/common/js/utils";
export default { export default {
name: "EnvironmentEdit", name: "EnvironmentEdit",
components: { components: {
EnvironmentTcpConfig, EnvironmentTcpConfig,
MsEnvironmentCommonConfig, MsEnvironmentCommonConfig,
MsEnvironmentHttpConfig, MsEnvironmentHttpConfig,
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables}, MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables
props: { },
environment: new Environment(), props: {
isReadOnly: { environment: new Environment(),
type: Boolean, isReadOnly: {
default: false type: Boolean,
}, default: false
}, },
data() { },
data() {
return { return {
result: {}, result: {},
envEnable: false, envEnable: false,
rules: { rules: {
name: [ name: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'}, {required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 64, message: this.$t('commons.input_limit', [1, 64]), 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() { headerSuggestions: REQUEST_HEADERS,
let isValidate = false; activeName: 'common'
this.$refs['environment'].validate((valid) => { }
if (valid && this.$refs.commonConfig.validate() && this.$refs.httpConfig.validate()) { },
isValidate = true; watch: {
} else { environment: function (o) {
isValidate = false; this.envEnable = o.enable;
} }
}); },
return isValidate; methods: {
}, save() {
_save(environment) { this.$refs['environment'].validate((valid) => {
let param = this.buildParam(environment); if (valid && this.$refs.commonConfig.validate() && this.$refs.httpConfig.validate()) {
let url = '/api/environment/add'; this._save(this.environment);
if (param.id) {
url = '/api/environment/update';
} }
this.result = this.$fileUpload(url, null, [], param, response => { });
if (!param.id) { },
environment.id = response.data; 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) {
if (!this.projectId) {
this.$warning(this.$t('api_test.select_project'));
return;
}
if (environment && environment.config && environment.config.commonConfig && environment.config.commonConfig.variables) {
let repeatKey = this.check(environment.config.commonConfig && environment.config.commonConfig.variables);
if (repeatKey !== "") {
this.$warning(this.$t('api_test.environment.common_config') + "【" + repeatKey + "】" + this.$t('load_test.param_is_duplicate'));
return;
}
}
let message = '';
if (environment && environment.config && environment.config.httpConfig && environment.config.httpConfig.conditions) {
environment.config.httpConfig.conditions.forEach(env => {
if (env.type === "MODULE" && env.details.length === 0) {
message += this.$t('load_test.domain') + ":" + env.socket + ":" + this.$t('api_test.environment.module_warning');
return;
} }
if (env.type === "PATH" && env.details) {
env.details.forEach(item => {
if (!item.name) {
message += this.$t('load_test.domain') + ":" + env.socket + ":" + this.$t('api_test.environment.path_warning');
return;
}
})
}
})
}
environment.config.commonConfig.variables.forEach(variable => {
if (variable.type === 'CSV' && variable.files.length === 0) {
message = this.$t('api_test.automation.csv_warning');
return;
}
})
if (message) {
this.$warning(message);
return;
}
let bodyFiles = this.geFiles(environment);
let variablesFiles = this.getVariablesFiles(environment);
let formData = new FormData();
if (bodyFiles) {
bodyFiles.forEach(f => {
formData.append("files", f);
})
}
if (variablesFiles) {
variablesFiles.forEach(f => {
formData.append("variablesFiles", f);
})
}
let param = this.buildParam(environment);
let url = '/api/environment/add';
if (param.id) {
url = '/api/environment/update';
}
formData.append('request', new Blob([JSON.stringify(param)], {type: "application/json"}));
let axiosRequestConfig = getUploadConfig(url, formData);
request(axiosRequestConfig, (response) => {
if (response.success) {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
}); this.$emit('refreshAfterSave'); //EnvironmentList.vue使
}, this.cancel()
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); }, error => {
return param; this.$emit('errorRefresh', error);
}, });
cancel() {
this.$emit('close');
},
clearValidate() {
this.$refs["environment"].clearValidate();
},
}, },
} geFiles(obj) {
let uploadFiles = [];
obj.uploadIds = [];
if (obj.config && obj.config.sslConfig && obj.config.sslConfig.files) {
obj.config.sslConfig.files.forEach(item => {
if (item.file && item.file.size > 0) {
if (!item.id) {
item.name = item.file.name;
item.id = getUUID();
}
obj.uploadIds.push(item.id);
uploadFiles.push(item.file);
}
})
}
return uploadFiles;
},
getVariablesFiles(obj) {
let variablesFiles = [];
obj.variablesFilesIds = [];
// csv
if (obj.config.commonConfig.variables) {
obj.config.commonConfig.variables.forEach(param => {
if (param.type === 'CSV' && param.files) {
param.files.forEach(item => {
if (item.file && item.file.name) {
if (!item.id) {
let fileId = getUUID().substring(0, 12);
item.name = item.file.name;
item.id = fileId;
}
obj.variablesFilesIds.push(item.id);
variablesFiles.push(item.file);
}
})
}
});
}
return variablesFiles;
},
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> </script>
<style scoped> <style scoped>
.el-main { .el-main {
border: solid 1px #EBEEF5; border: solid 1px #EBEEF5;
margin-left: 200px; margin-left: 200px;
min-height: 400px; min-height: 400px;
max-height: 700px; max-height: 700px;
} }
.el-row { .el-row {
margin-bottom: 15px; margin-bottom: 15px;
} }
.environment-footer { .environment-footer {
margin-top: 15px; margin-top: 15px;
float: right; float: right;
} }
span { span {
display: block; display: block;
margin-bottom: 15px; margin-bottom: 15px;
} }
span:not(:first-child) { span:not(:first-child) {
margin-top: 15px; margin-top: 15px;
} }
</style> </style>

View File

@ -274,22 +274,24 @@ export default {
let params = data.split("\n"); let params = data.split("\n");
let keyValues = []; let keyValues = [];
params.forEach(item => { params.forEach(item => {
let line = []; if (item) {
line[0] = item.substring(0,item.indexOf(":")); let line = [];
line[1] = item.substring(item.indexOf(":")+1,item.length); line[0] = item.substring(0, item.indexOf(":"));
let required = false; line[1] = item.substring(item.indexOf(":") + 1, item.length);
keyValues.unshift(new KeyValue({ let required = false;
name: line[0], keyValues.push(new KeyValue({
required: required, name: line[0],
value: line[1], required: required,
description: line[2], value: line[1],
type: "text", description: line[2],
valid: false, type: "text",
file: false, valid: false,
encode: true, file: false,
enable: true, encode: true,
contentType: "text/plain" enable: true,
})); contentType: "text/plain"
}));
}
}) })
keyValues.forEach(item => { keyValues.forEach(item => {
this.format(this.body.kvs, item); this.format(this.body.kvs, item);

View File

@ -19,51 +19,51 @@
<el-switch active-text="JSON-SCHEMA" v-model="body.format" @change="formatChange" active-value="JSON-SCHEMA"/> <el-switch active-text="JSON-SCHEMA" v-model="body.format" @change="formatChange" active-value="JSON-SCHEMA"/>
</div> </div>
<ms-json-code-edit <ms-json-code-edit
v-if="body.format==='JSON-SCHEMA'" v-if="body.format==='JSON-SCHEMA'"
:body="body" :body="body"
:show-mock-vars="true" :show-mock-vars="true"
ref="jsonCodeEdit"/> ref="jsonCodeEdit"/>
<ms-code-edit <ms-code-edit
v-else-if="codeEditActive && loadIsOver" v-else-if="codeEditActive && loadIsOver"
:read-only="isReadOnly" :read-only="isReadOnly"
:data.sync="body.raw" :data.sync="body.raw"
:modes="modes" :modes="modes"
:mode="'json'" :mode="'json'"
height="90%" height="90%"
ref="codeEdit"/> ref="codeEdit"/>
</div> </div>
<div class="ms-body" v-if="body.type == 'fromApi'"> <div class="ms-body" v-if="body.type == 'fromApi'">
<ms-code-edit <ms-code-edit
:read-only="true" :read-only="true"
:data.sync="body.apiRspRaw" :data.sync="body.apiRspRaw"
:modes="modes" :modes="modes"
:mode="'text'" :mode="'text'"
v-if="loadIsOver" v-if="loadIsOver"
height="90%" height="90%"
ref="fromApiCodeEdit"/> ref="fromApiCodeEdit"/>
</div> </div>
<div class="ms-body" v-if="body.type == 'XML'"> <div class="ms-body" v-if="body.type == 'XML'">
<el-input v-model="body.xmlHeader" size="small" style="width: 400px;margin-bottom: 5px"/> <el-input v-model="body.xmlHeader" size="small" style="width: 400px;margin-bottom: 5px"/>
<ms-code-edit <ms-code-edit
:read-only="isReadOnly" :read-only="isReadOnly"
:data.sync="body.xmlRaw" :data.sync="body.xmlRaw"
:modes="modes" :modes="modes"
:mode="'xml'" :mode="'xml'"
v-if="loadIsOver" v-if="loadIsOver"
height="90%" height="90%"
ref="codeEdit"/> ref="codeEdit"/>
</div> </div>
<div class="ms-body" v-if="body.type == 'Raw'"> <div class="ms-body" v-if="body.type == 'Raw'">
<ms-code-edit <ms-code-edit
:read-only="isReadOnly" :read-only="isReadOnly"
:data.sync="body.raw" :data.sync="body.raw"
:modes="modes" :modes="modes"
v-if="loadIsOver" v-if="loadIsOver"
height="90%" height="90%"
ref="codeEdit"/> ref="codeEdit"/>
</div> </div>
<batch-add-parameter @batchSave="batchSave" ref="batchAddParameter"/> <batch-add-parameter @batchSave="batchSave" ref="batchAddParameter"/>
@ -309,22 +309,24 @@ export default {
let params = data.split("\n"); let params = data.split("\n");
let keyValues = []; let keyValues = [];
params.forEach(item => { params.forEach(item => {
let line = []; if (item) {
line[0] = item.substring(0, item.indexOf(":")); let line = [];
line[1] = item.substring(item.indexOf(":") + 1, item.length); line[0] = item.substring(0, item.indexOf(":"));
let required = false; line[1] = item.substring(item.indexOf(":") + 1, item.length);
keyValues.unshift(new KeyValue({ let required = false;
name: line[0], keyValues.push(new KeyValue({
required: required, name: line[0],
value: line[1], required: required,
description: line[2], value: line[1],
type: "text", description: line[2],
valid: false, type: "text",
file: false, valid: false,
encode: true, file: false,
enable: true, encode: true,
contentType: "text/plain" enable: true,
})); contentType: "text/plain"
}));
}
}) })
keyValues.forEach(item => { keyValues.forEach(item => {
this.format(this.body.kvs, item); this.format(this.body.kvs, item);

View File

@ -19,14 +19,14 @@
</el-link> </el-link>
</el-row> </el-row>
<ms-api-key-value <ms-api-key-value
@editScenarioAdvance="editScenarioAdvance" @editScenarioAdvance="editScenarioAdvance"
:scenario-definition="scenarioDefinition" :scenario-definition="scenarioDefinition"
:show-desc="true" :show-desc="true"
:is-read-only="isReadOnly" :is-read-only="isReadOnly"
:isShowEnable="isShowEnable" :isShowEnable="isShowEnable"
:suggestions="headerSuggestions" :suggestions="headerSuggestions"
:items="headers" :items="headers"
:need-mock="true" v-if="activeName === 'headers'"/> :need-mock="true" v-if="activeName === 'headers'"/>
</el-tab-pane> </el-tab-pane>
<!--query 参数--> <!--query 参数-->
@ -45,13 +45,13 @@
</el-link> </el-link>
</el-row> </el-row>
<ms-api-variable <ms-api-variable
@editScenarioAdvance="editScenarioAdvance" @editScenarioAdvance="editScenarioAdvance"
:scenario-definition="scenarioDefinition" :scenario-definition="scenarioDefinition"
:with-mor-setting="true" :with-mor-setting="true"
:is-read-only="isReadOnly" :is-read-only="isReadOnly"
:isShowEnable="isShowEnable" :isShowEnable="isShowEnable"
:parameters="request.arguments" :parameters="request.arguments"
v-if="activeName === 'parameters'" v-if="activeName === 'parameters'"
/> />
</el-tab-pane> </el-tab-pane>
@ -72,13 +72,13 @@
</el-link> </el-link>
</el-row> </el-row>
<ms-api-variable <ms-api-variable
@editScenarioAdvance="editScenarioAdvance" @editScenarioAdvance="editScenarioAdvance"
:scenario-definition="scenarioDefinition" :scenario-definition="scenarioDefinition"
:with-mor-setting="true" :with-mor-setting="true"
:is-read-only="isReadOnly" :is-read-only="isReadOnly"
:isShowEnable="isShowEnable" :isShowEnable="isShowEnable"
:parameters="request.rest" :parameters="request.rest"
v-if="activeName === 'rest'" v-if="activeName === 'rest'"
/> />
</el-tab-pane> </el-tab-pane>
@ -105,17 +105,17 @@
</el-tooltip> </el-tooltip>
<ms-api-auth-config <ms-api-auth-config
:is-read-only="isReadOnly" :is-read-only="isReadOnly"
:request="request" :request="request"
v-if="activeName === 'authConfig'" v-if="activeName === 'authConfig'"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.other_config')" name="advancedConfig"> <el-tab-pane :label="$t('api_test.definition.request.other_config')" name="advancedConfig">
<ms-api-advanced-config <ms-api-advanced-config
:is-read-only="isReadOnly" :is-read-only="isReadOnly"
:request="request" :request="request"
v-if="activeName === 'advancedConfig'" v-if="activeName === 'advancedConfig'"
/> />
</el-tab-pane> </el-tab-pane>
@ -128,13 +128,13 @@
</div> </div>
</span> </span>
<ms-jmx-step <ms-jmx-step
:request="request" :request="request"
:apiId="request.id" :apiId="request.id"
:response="response" :response="response"
:tab-type="'pre'" :tab-type="'pre'"
:scenarioId="scenarioId" :scenarioId="scenarioId"
ref="preStep" ref="preStep"
v-if="activeName === 'preOperate'" v-if="activeName === 'preOperate'"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.post_operation')" name="postOperate" v-if="showScript"> <el-tab-pane :label="$t('api_test.definition.request.post_operation')" name="postOperate" v-if="showScript">
@ -145,13 +145,13 @@
</div> </div>
</span> </span>
<ms-jmx-step <ms-jmx-step
:request="request" :request="request"
:apiId="request.id" :apiId="request.id"
:response="response" :response="response"
:tab-type="'post'" :tab-type="'post'"
:scenarioId="scenarioId" :scenarioId="scenarioId"
ref="postStep" ref="postStep"
v-if="activeName === 'postOperate'" v-if="activeName === 'postOperate'"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.assertions_rule')" name="assertionsRule" v-if="showScript"> <el-tab-pane :label="$t('api_test.definition.request.assertions_rule')" name="assertionsRule" v-if="showScript">
@ -162,14 +162,14 @@
</div> </div>
</span> </span>
<ms-jmx-step <ms-jmx-step
:request="request" :request="request"
:apiId="request.id" :apiId="request.id"
:scenarioId="scenarioId" :scenarioId="scenarioId"
:response="response" :response="response"
@reload="reloadBody" @reload="reloadBody"
:tab-type="'assertionsRule'" :tab-type="'assertionsRule'"
ref="assertionsRule" ref="assertionsRule"
v-if="activeName === 'assertionsRule'"/> v-if="activeName === 'assertionsRule'"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -424,13 +424,13 @@ export default {
if (isAdd) { if (isAdd) {
switch (this.activeName) { switch (this.activeName) {
case "parameters": case "parameters":
this.request.arguments.unshift(obj); this.request.arguments.splice(this.request.arguments.indexOf(h => !h.name), 0, obj);
break; break;
case "rest": case "rest":
this.request.rest.unshift(obj); this.request.rest.splice(this.request.rest.indexOf(h => !h.name), 0, obj);
break; break;
case "headers": case "headers":
this.request.headers.unshift(obj); this.request.headers.splice(this.request.headers.indexOf(h => !h.name), 0, obj);
break; break;
default: default:
break; break;
@ -443,20 +443,22 @@ export default {
let params = data.split("\n"); let params = data.split("\n");
let keyValues = []; let keyValues = [];
params.forEach(item => { params.forEach(item => {
let line = item.split(/|:/); if (item) {
let values = item.split(line[0] + ":"); let line = item.split(/|:/);
let required = false; let values = item.split(line[0] + ":");
keyValues.unshift(new KeyValue({ let required = false;
name: line[0], keyValues.push(new KeyValue({
required: required, name: line[0],
value: values[1], required: required,
type: "text", value: values[1],
valid: false, type: "text",
file: false, valid: false,
encode: true, file: false,
enable: true, encode: true,
contentType: "text/plain" enable: true,
})); contentType: "text/plain"
}));
}
}) })
keyValues.forEach(item => { keyValues.forEach(item => {

View File

@ -8,9 +8,9 @@
:data="environments" :item-operators="environmentOperators" :add-fuc="addEnvironment" :data="environments" :item-operators="environmentOperators" :add-fuc="addEnvironment"
:env-add-permission="ENV_CREATE" :env-add-permission="ENV_CREATE"
:delete-fuc="openDelEnv" @itemSelected="environmentSelected" ref="environmentItems"/> :delete-fuc="openDelEnv" @itemSelected="environmentSelected" ref="environmentItems"/>
<environment-edit :if-create="ifCreate" :project-id="projectId" :environment="currentEnvironment" <environment-edit :if-create="ifCreate" :environment="currentEnvironment"
ref="environmentEdit" :is-read-only="isReadOnly" ref="environmentEdit" :is-read-only="isReadOnly"
@confirm="save" @confirm="save" :is-project="true"
@close="close"/> @close="close"/>
</el-container> </el-container>
</el-dialog> </el-dialog>

View File

@ -112,7 +112,7 @@ export default {
let line = item.split(/|:/); let line = item.split(/|:/);
let values = item.split(line[0] + ":"); let values = item.split(line[0] + ":");
let required = false; let required = false;
keyValues.unshift(new KeyValue({ keyValues.push(new KeyValue({
name: line[0], name: line[0],
required: required, required: required,
value: values[1], value: values[1],

View File

@ -0,0 +1,399 @@
<template>
<div>
<div>
<div style="padding-bottom: 10px;float: left">
<el-input :placeholder="$t('commons.search_by_name')" size="mini" v-model="selectVariable"
@change="filter"
@keyup.enter="filter">
</el-input>
</div>
<div style="padding-bottom: 10px; float: right;">
<ms-table-button v-permission="['PROJECT_ENVIRONMENT:READ+IMPORT']" icon="el-icon-box"
:content="$t('commons.import')" @click="importJSON"/>
<ms-table-button v-permission="['PROJECT_ENVIRONMENT:READ+EXPORT']" icon="el-icon-box"
:content="$t('commons.export')" @click="exportJSON"/>
<el-link style="margin-left: 10px" @click="batchAdd" type="primary" :disabled="isReadOnly">
{{ $t("commons.batch_add") }}
</el-link>
</div>
</div>
<div
style="border:1px #DCDFE6 solid; min-height: 300px;border-radius: 4px ;width: 99% ;margin-top: 10px; clear: both">
<ms-table
v-loading="loading"
row-key="id"
:data="variables"
:total="items.length"
:screen-height="screenHeight"
:batch-operators="batchButtons"
:remember-order="true"
:highlightCurrentRow="true"
@refresh="onChange"
ref="variableTable">
<ms-table-column
prop="num"
sortable
label="ID"
min-width="60">
</ms-table-column>
<ms-table-column prop="name" :label="$t('api_test.variable_name')" min-width="200" sortable>
<template slot-scope="scope">
<el-input
v-model="scope.row.name" size="mini"
maxlength="200" :placeholder="$t('api_test.variable_name')" show-word-limit
@change="change"/>
</template>
</ms-table-column>
<ms-table-column prop="type" :label="$t('test_track.case.type')" min-width="140" sortable>
<template slot-scope="scope">
<el-select v-model="scope.row.type" :placeholder="$t('commons.please_select')" size="mini"
@change="changeType(scope.row)">
<el-option v-for="item in typeSelectOptions " :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</ms-table-column>
<ms-table-column prop="value" :label="$t('api_test.value')"
min-width="200px" sortable>
<template slot-scope="scope">
<el-input v-model="scope.row.value" size="mini" v-if="scope.row.type !=='CSV'"
:placeholder="valueText(scope.row)"
:disabled="scope.row.type === 'COUNTER' || scope.row.type === 'RANDOM'"/>
<csv-file-upload :parameter="scope.row" v-if="scope.row.type ==='CSV'"/>
</template>
</ms-table-column>
<ms-table-column prop="description" :label="$t('commons.remark')"
min-width="160" sortable>
<template slot-scope="scope">
<el-input v-model="scope.row.description" size="mini"/>
</template>
</ms-table-column>
<ms-table-column :label="$t('commons.operating')" width="150" fixed="right">
<template v-slot:default="scope">
<span>
<el-switch v-model="scope.row.enable" size="mini"/>
<el-tooltip effect="dark" :content="$t('commons.remove')" placement="top-start">
<el-button icon="el-icon-delete" type="danger" circle size="mini" style="margin-left: 10px"
@click="remove(scope.row)" v-if="scope.row.name"/>
</el-tooltip>
<el-tooltip effect="dark" :content="$t('schema.adv_setting')" placement="top-start">
<el-button icon="el-icon-setting" circle size="mini" style="margin-left: 10px"
@click="openSetting(scope.row)" v-if="scope.row.type !=='LIST'"/>
</el-tooltip>
</span>
</template>
</ms-table-column>
</ms-table>
</div>
<batch-add-parameter @batchSave="batchSave" ref="batchAdd"/>
<api-variable-setting ref="apiVariableSetting"></api-variable-setting>
<variable-import ref="variableImport" @mergeData="mergeData"></variable-import>
</div>
</template>
<script>
import {KeyValue} from "@/business/components/api/test/model/ScenarioModel";
import MsApiVariableInput from "@/business/components/api/automation/scenario/ApiVariableInput";
import BatchAddParameter from "@/business/components/api/definition/components/basis/BatchAddParameter";
import MsTableButton from "@/business/components/common/components/MsTableButton";
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/MsTableColumn";
import ApiVariableSetting from "@/business/components/api/test/components/environment/ApiVariableSetting";
import CsvFileUpload from "@/business/components/api/test/components/environment/CsvFileUpload";
import {downloadFile, getUUID} from "@/common/js/utils";
import VariableImport from "@/business/components/api/test/components/environment/VariableImport";
export default {
name: "MsApiScenarioVariables",
components: {
BatchAddParameter,
MsApiVariableInput,
MsTableButton,
MsTable,
MsTableColumn,
ApiVariableSetting,
CsvFileUpload,
VariableImport
},
props: {
description: String,
items: Array,
isReadOnly: {
type: Boolean,
default: false
},
showVariable: {
type: Boolean,
default: true
},
showCopy: {
type: Boolean,
default: true
},
},
data() {
return {
loading: false,
screenHeight: '400px',
batchButtons: [
{
name: this.$t('api_test.definition.request.batch_delete'),
handleClick: this.handleDeleteBatch,
},
],
typeSelectOptions: [
{value: 'CONSTANT', label: this.$t('api_test.automation.constant')},
{value: 'LIST', label: this.$t('test_track.case.list')},
{value: 'CSV', label: 'CSV'},
{value: 'COUNTER', label: this.$t('api_test.automation.counter')},
{value: 'RANDOM', label: this.$t('api_test.automation.random')},
],
variables: {},
selectVariable: '',
editData: {},
}
},
watch: {
items: {
handler(v) {
this.variables = v;
this.sortParameters();
},
immediate: true,
deep: true
}
},
methods: {
remove: function (index) {
const dataIndex = this.variables.findIndex(d => d.name === index.name);
this.variables.splice(dataIndex, 1);
this.$emit('change', this.variables);
},
change: function () {
let isNeedCreate = true;
let removeIndex = -1;
let repeatKey = "";
this.variables.forEach((item, index) => {
this.variables.forEach((row, rowIndex) => {
if (item.name === row.name && index !== rowIndex) {
repeatKey = item.name;
}
});
if (!item.name && !item.value) {
//
if (index !== this.items.length - 1) {
removeIndex = index;
}
//
isNeedCreate = false;
}
});
if (repeatKey !== "") {
this.$warning(this.$t('api_test.environment.common_config') + "【" + repeatKey + "】" + this.$t('load_test.param_is_duplicate'));
}
if (isNeedCreate && !repeatKey) {
this.variables.push(new KeyValue({enable: true, id: getUUID(), type: 'CONSTANT'}));
}
this.$emit('change', this.variables);
// TODO key
},
changeType(data) {
if (!data.delimiter || (!data.files && data.files.length === 0) || !data.quotedData) {
data.delimiter = ',';
data.files = [];
data.quotedData = 'false';
}
},
valueText(data) {
switch (data.type) {
case 'LIST':
return this.$t('api_test.environment.list_info');
case 'CONSTANT':
return this.$t('api_test.value');
case 'COUNTER':
case 'RANDOM':
return this.$t('api_test.environment.advanced_setting');
default:
return this.$t('api_test.value');
}
},
querySearch(queryString, cb) {
let restaurants = [{value: "UTF-8"}, {value: "UTF-16"}, {value: "GB2312"}, {value: "ISO-8859-15"}, {value: "US-ASCll"}];
let results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants;
// callback
cb(results);
},
sortParameters() {
let index = 1;
this.variables.forEach(item => {
item.num = index;
if (!item.type || item.type === 'text') {
item.type = 'CONSTANT';
}
if (!item.id) {
item.id = getUUID();
}
if (item.remark) {
item.description = item.remark;
}
index++;
});
},
handleDeleteBatch() {
this.$alert(this.$t('api_test.environment.variables_delete_info') + ' ' + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = this.$refs.variableTable.selectRows;
ids.forEach(row => {
if (row.name) {
const index = this.variables.findIndex(d => d.name === row.name);
this.variables.splice(index, 1);
}
});
this.sortParameters();
this.$refs.variableTable.cancelCurrentRow();
this.$refs.variableTable.clear();
}
}
});
},
filter() {
let datas = [];
this.variables.forEach(item => {
if (this.selectVariable && this.selectVariable != "" && item.name) {
if (item.name.toLowerCase().indexOf(this.selectVariable.toLowerCase()) == -1) {
item.hidden = true;
} else {
item.hidden = undefined;
}
} else {
item.hidden = undefined;
}
datas.push(item);
});
this.variables = datas;
},
openSetting(data) {
this.$refs.apiVariableSetting.open(data);
},
isDisable: function (index) {
return this.items.length - 1 === index;
},
_handleBatchVars(data) {
let params = data.split("\n");
let keyValues = [];
params.forEach(item => {
if (item) {
let line = item.split(/|:/);
let values = item.split(line[0] + ":");
let required = false;
keyValues.push(new KeyValue({
name: line[0],
required: required,
value: values[1],
type: 'CONSTANT',
valid: false,
file: false,
encode: true,
enable: true,
description: undefined
}));
}
});
return keyValues;
},
batchAdd() {
this.$refs.batchAdd.open();
},
batchSave(data) {
if (data) {
let keyValues = this._handleBatchVars(data);
keyValues.forEach(keyValue => {
let isAdd = true;
this.variables.forEach(item => {
if (item.name === keyValue.name) {
item.value = keyValue.value;
isAdd = false;
}
})
if (isAdd) {
this.variables.splice(this.variables.indexOf(i => !i.name), 0, keyValue);
}
})
}
},
onChange() {
this.sortParameters();
},
exportJSON() {
if (this.$refs.variableTable.selectIds.length < 1) {
this.$warning(this.$t('api_test.environment.select_variable'));
return;
}
let variablesJson = [];
let messages = '';
let rows = this.$refs.variableTable.selectRows;
rows.forEach(row => {
if (row.type === 'CSV') {
messages = this.$t('variables.csv_download')
}
if (row.name) {
variablesJson.push(row);
}
})
if (messages !== '') {
this.$warning(messages);
return;
}
downloadFile('MS_' + variablesJson.length + '_Environments_variables.json', JSON.stringify(variablesJson));
},
importJSON() {
this.$refs.variableImport.open();
},
mergeData(data, modeId) {
JSON.parse(data).map(importData => {
importData.id = getUUID();
importData.enable = true;
importData.showMore = false;
let sameNameIndex = this.variables.findIndex(d => d.name === importData.name);
if (sameNameIndex !== -1) {
if (modeId === 'fullCoverage') {
this.variables.splice(sameNameIndex, 1, importData);
}
} else {
this.variables.splice(this.variables.length - 1, 0, importData);
}
})
}
},
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>

View File

@ -0,0 +1,306 @@
<template>
<div>
<el-tabs tab-position="top" @tab-click="selectTab">
<el-tab-pane :label="$t('api_test.request.parameters_advance_mock')">
<el-row type="flex" :gutter="20">
<el-col :span="6" class="col-height">
<div>
<el-input size="small" v-model="filterText"
:placeholder="$t('api_test.request.parameters_mock_filter_tips')"/>
<el-tree class="filter-tree" ref="tree" :data="mockFuncs" :props="treeProps"
default-expand-all @node-click="selectVariable"
:filter-node-method="filterNode"></el-tree>
</div>
</el-col>
<el-col :span="6" v-for="(itemFunc, itemIndex) in mockVariableFuncs" :key="itemIndex">
<div v-for="(func, funcIndex) in funcs"
:key="`${itemIndex}-${funcIndex}`">
<el-row>
<el-col :span="12">
<el-radio size="mini" v-model="itemFunc.name" :label="func.name"
@change="methodChange(itemFunc, func)" @click.native.prevent="radioClick(itemFunc, func)"/>
</el-col>
<el-col :span="12" v-if="itemFunc.name === func.name">
<div v-for="(p, pIndex) in itemFunc.params" :key="`${itemIndex}-${funcIndex}-${pIndex}`">
<el-input :placeholder="p.name" size="mini" v-model="p.value" @change="showPreview"/>
</div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.variable')">
<el-row>
<el-col :span="6" class="col-height">
<div v-if="environment">
<p>{{ $t('api_test.environment.environment') }}</p>
<el-tree :data="environmentParams" :props="treeProps" @node-click="selectVariable"></el-tree>
</div>
<div v-if="scenario">
<p>{{ $t('api_test.scenario.scenario') }}</p>
<el-tree :data="scenarioParams" :props="treeProps" @node-click="selectVariable"></el-tree>
</div>
<div v-if="preRequestParams">
<p>{{ $t('api_test.request.parameters_pre_request') }}</p>
<el-tree :data="preRequestParams" :props="treeProps" @node-click="selectVariable"></el-tree>
</div>
</el-col>
<el-col :span="18" class="col-height">
<div>
<h1>{{ $t('api_test.request.jmeter_func') }}</h1>
<el-table border :data="jmeterFuncs" class="adjust-table table-content" height="400">
<el-table-column prop="type" label="Type" width="150"/>
<el-table-column prop="name" label="Functions" width="250"/>
<el-table-column prop="description" label="Description"/>
</el-table>
</div>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
<el-form>
<el-form-item>
<el-input :placeholder="valueText" size="small"
v-model="itemValue"/>
</el-form-item>
</el-form>
<div style="padding-top: 10px;">
<el-row type="flex" align="middle">
<el-col :span="12">
<el-button size="small" type="info" plain @click="addFunc()" v-if="currentTab === 0">
{{ $t('api_test.request.parameters_advance_add_func') }}
</el-button>
<el-button size="small" type="success" plain @click="showPreview()" v-if="currentTab === 0">
{{ $t('api_test.request.parameters_preview') }}
</el-button>
</el-col>
<el-col>
<div> {{ itemValuePreview }}</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import {calculate, Scenario} from "@/business/components/api/test/model/ScenarioModel";
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
export default {
name: "MsApiVariableAdvance",
props: {
parameters: Array,
environment: Object,
scenario: Scenario,
currentItem: Object,
appendToBody: {
type: Boolean,
default() {
return false;
}
},
},
data() {
return {
itemValueVisible: false,
filterText: '',
environmentParams: [],
scenarioParams: [],
preRequests: [],
preRequestParams: [],
treeProps: {children: 'children', label: 'name'},
currentTab: 0,
itemValue: null,
itemValuePreview: null,
funcs: [
{name: "md5"},
{name: "base64"},
{name: "unbase64"},
{
name: "substr",
params: [{name: "start"}, {name: "length"}]
},
{
name: "concat",
params: [{name: "suffix"}]
},
{name: "lconcat", params: [{name: "prefix"}]},
{name: "sha1"},
{name: "sha224"},
{name: "sha256"},
{name: "sha384"},
{name: "sha512"},
{name: "lower"},
{name: "upper"},
{name: "length"},
{name: "number"}
],
mockFuncs: MOCKJS_FUNC.map(f => {
return {name: f.name, value: f.name}
}),
jmeterFuncs: JMETER_FUNC,
mockVariableFuncs: [],
jmeterVariableFuncs: [],
}
},
computed: {
valueText() {
return this.valuePlaceholder || this.$t("api_test.value");
}
},
mounted() {
this.prepareData();
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
},
},
methods: {
open() {
this.itemValueVisible = true;
},
prepareData() {
if (this.scenario) {
let variables = this.scenario.variables;
this.scenarioParams = [
{
name: this.scenario.name,
children: variables.filter(v => v.name).map(v => {
return {name: v.name, value: '${' + v.name + '}'}
}),
}
];
if (this.environment) {
let variables = this.environment.config.commonConfig.variables;
this.environmentParams = [
{
name: this.environment.name,
children: variables.filter(v => v.name).map(v => {
return {name: v.name, value: '${' + v.name + '}'}
}),
}
];
}
let i = this.scenario.requests.indexOf(this.request);
this.preRequests = this.scenario.requests.slice(0, i);
this.preRequests.forEach(r => {
let js = r.extract.json.map(v => {
return {name: v.variable, value: v.value}
});
let xs = r.extract.xpath.map(v => {
return {name: v.variable, value: v.value}
});
let rx = r.extract.regex.map(v => {
return {name: v.variable, value: v.value}
});
let vs = [...js, ...xs, ...rx];
if (vs.length > 0) {
this.preRequestParams.push({name: r.name, children: vs});
}
});
}
},
filterNode(value, data) {
if (!value) {
return true;
}
return data.name.indexOf(value) !== -1;
},
selectVariable(node) {
this.itemValue = node.value;
},
selectTab(tab) {
this.currentTab = +tab.index;
this.itemValue = null;
this.itemValuePreview = null;
},
showPreview() {
//
if (!this.itemValue) {
return;
}
let index = this.itemValue.indexOf("|");
if (index > -1) {
this.itemValue = this.itemValue.substring(0, index).trim();
}
this.mockVariableFuncs.forEach(f => {
if (!f.name) {
return;
}
this.itemValue += "|" + f.name;
if (f.params) {
this.itemValue += ":" + f.params.map(p => p.value).join(",");
}
});
this.itemValuePreview = calculate(this.itemValue);
},
methodChange(itemFunc, func) {
let index = this.mockVariableFuncs.indexOf(itemFunc);
this.mockVariableFuncs = this.mockVariableFuncs.slice(0, index);
// deep copy
this.mockVariableFuncs.push(JSON.parse(JSON.stringify(func)));
this.showPreview();
},
radioClick(itemFunc, func) {
if (itemFunc.name === func.name) {
let index = this.mockVariableFuncs.indexOf(itemFunc);
this.mockVariableFuncs = this.mockVariableFuncs.slice(0, index);
this.mockVariableFuncs.push({name: '', params: []});
let valindex = this.itemValue.indexOf('|' + func.name);
this.itemValue = this.itemValue.slice(0, valindex);
} else {
this.methodChange(itemFunc, func);
}
},
addFunc() {
if (this.itemValue.indexOf('@') == -1) {
this.itemValue = '@' + this.itemValue;
} else {
this.itemValue = this.itemValue;
}
if (this.mockVariableFuncs.length > 4) {
this.$info(this.$t('api_test.request.parameters_advance_add_func_limit'));
return;
}
if (this.mockVariableFuncs.length > 0) {
let func = this.mockVariableFuncs[this.mockVariableFuncs.length - 1];
if (!func.name) {
this.$warning(this.$t('api_test.request.parameters_advance_add_func_error'));
return;
}
if (func.params) {
for (let j = 0; j < func.params.length; j++) {
if (!func.params[j].value) {
this.$warning(this.$t('api_test.request.parameters_advance_add_param_error'));
return;
}
}
}
}
this.mockVariableFuncs.push({name: '', params: []});
},
saveAdvanced() {
if (this.itemValue && this.itemValue.indexOf('@') == -1) {
this.currentItem.value = '@' + this.itemValue;
} else {
this.currentItem.value = this.itemValue;
}
this.itemValueVisible = false;
this.mockVariableFuncs = [];
this.$emit('advancedRefresh', this.currentItem.value);
}
}
}
</script>
<style scoped>
.col-height {
height: 40vh;
overflow: auto;
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<ms-edit-dialog
:visible.sync="visible"
width="700px"
:title="$t('schema.adv_setting')"
:with-footer="false"
append-to-body
:close-on-click-modal="true">
<ms-api-variable-advance v-if="editData.type=='CONSTANT'" ref="variableAdvance" :current-item="editData"
@advancedRefresh="reload"/>
<ms-edit-counter v-if="editData.type=='COUNTER'" ref="counter" :editData.sync="editData"/>
<ms-edit-random v-if="editData.type=='RANDOM'" ref="random" :editData.sync="editData"/>
<ms-edit-csv v-if="editData.type=='CSV'" ref="csv" :editData.sync="editData"/>
<template v-slot:footer>
<ms-dialog-footer
@cancel="handleCancel"
@confirm="handleConfirm"
/>
</template>
</ms-edit-dialog>
</template>
<script>
import MsEditDialog from "@/business/components/common/components/MsEditDialog";
import MsEditConstant from "@/business/components/api/automation/scenario/variable/EditConstant";
import MsEditCounter from "@/business/components/api/automation/scenario/variable/EditCounter";
import MsEditRandom from "@/business/components/api/automation/scenario/variable/EditRandom";
import MsEditListValue from "@/business/components/api/automation/scenario/variable/EditListValue";
import MsEditCsv from "@/business/components/api/automation/scenario/variable/EditCsv";
import MsApiVariableAdvance from "@/business/components/api/test/components/environment/ApiVariableAdvance";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
export default {
name: "ApiVariableSetting",
components: {
MsEditDialog,
MsEditConstant,
MsEditCounter,
MsEditRandom,
MsEditListValue,
MsEditCsv,
MsApiVariableAdvance,
MsDialogFooter
},
data() {
return {
visible: false,
data: {},
editData: {}
}
},
methods: {
open(item) {
this.visible = true;
this.editData = item;
},
handleConfirm() {
if (this.editData.type === 'CONSTANT') {
this.$refs.variableAdvance.saveAdvanced();
}
if (this.editData.type === 'CSV' && this.$refs.csv) {
if (this.editData.files.length === 0) {
this.$warning(this.$t('api_test.automation.csv_warning'));
return;
}
}
this.visible = false;
},
handleCancel() {
this.visible = false;
},
reload() {
this.isActive = false;
this.$nextTick(() => {
this.isActive = true;
});
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,273 @@
<template>
<span>
<el-row>
<el-col :span="18">
<el-upload
action="#"
class="api-body-upload"
list-type="picture-card"
:file-list="parameter.files"
:beforeUpload="uploadValidate"
:on-exceed="exceed"
:limit="1"
ref="upload">
<div class="upload-default" @click.stop>
<el-popover
placement="right"
trigger="hover">
<div>
<el-upload
action="#"
class="ms-body-upload"
:http-request="upload"
:limit="1"
:on-exceed="exceed"
:beforeUpload="uploadValidate"
ref="uploadLocal">
<el-button type="text"> {{ $t('permission.project_file.local_upload') }}</el-button>
<span slot="file"/>
</el-upload>
</div>
<el-button type="text" @click="associationFile">{{
$t('permission.project_file.associated_files')
}}</el-button>
<i class="el-icon-plus" slot="reference"/>
</el-popover>
</div>
<div class="upload-item" slot="file" slot-scope="{file}">
<span>{{ file.file && file.file.name ? file.file.name : file.name }}</span>
<span class="el-upload-list__item-actions" v-if="file.storage === 'FILE_REF'">
<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleUnlock(file)">
<i class="el-icon-unlock"/>
</span>
</span>
<span class="el-upload-list__item-actions" v-else>
<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleUpload(file)">
<i class="el-icon-upload" style="font-size: 23px"/>
</span>
<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
<i class="el-icon-delete"/>
</span>
</span>
</div>
</el-upload>
</el-col>
</el-row>
<ms-file-batch-move ref="module" @setModuleId="setModuleId"/>
<ms-file-metadata-list ref="metadataList" @checkRows="checkRows"/>
</span>
</template>
<script>
import {downloadFile} from "@/common/js/utils";
import MsFileBatchMove from "@/business/components/project/menu/file/module/FileBatchMove";
import MsFileMetadataList from "@/business/components/project/menu/file/quote/QuoteFileList";
import {getCurrentProjectID, getUUID} from "@/common/js/utils";
export default {
name: "MsCsvFileUpload",
data() {
return {
disabled: false,
};
},
components: {
MsFileBatchMove,
MsFileMetadataList
},
props: {
parameter: Object,
default() {
return {}
}
},
methods: {
setModuleId(moduleId) {
let files = [];
if (this.file && this.file.file) {
files.push(this.file.file);
}
let request = {
id: getUUID(),
resourceId: this.id,
moduleId: moduleId,
projectId: getCurrentProjectID(),
fileName: this.file.name
};
this.$fileUpload("/file/metadata/api/upload", null, files, request, (response) => {
this.$success(this.$t("organization.integration.successful_operation"));
});
},
handleUpload(file) {
this.$refs.module.init();
this.file = file;
},
associationFile() {
this.$refs.metadataList.open();
},
checkRows(rows) {
if (rows && rows.size !== 1 || this.parameter.files.length > 0) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
return;
}
rows.forEach(item => {
if (!item.type || item.type.toLowerCase() !== "csv") {
this.$warning(this.$t('variables.cvs_info'));
return;
}
let file = {
name: item.name,
id: getUUID(),
fileId: item.id,
storage: "FILE_REF",
projectId: item.projectId,
fileType: item.type
};
this.parameter.files.push(file);
})
},
handleUnlock(file) {
for (let i = 0; i < this.parameter.files.length; i++) {
let fileName = file.file ? file.file.name : file.name;
let paramFileName = this.parameter.files[i].file ?
this.parameter.files[i].file.name : this.parameter.files[i].name;
if (fileName === paramFileName) {
this.parameter.files.splice(i, 1);
this.$refs.upload.handleRemove(file);
break;
}
}
},
download() {
//
if (this.parameter.files && this.parameter.files.length > 0 && this.parameter.files[0].file) {
downloadFile(this.parameter.files[0].file.name, this.parameter.files[0].file);
}
//
if (this.parameter.files && this.parameter.files.length > 0 && !this.parameter.files[0].file) {
let file = this.parameter.files[0];
let conf = {
url: "/api/automation/file/download",
method: 'post',
data: file,
responseType: 'blob',
};
if (file.storage === "FILE_REF") {
conf = {
url: "/file/metadata/download/" + file.fileId,
method: 'get',
responseType: 'blob',
};
}
this.result = this.$request(conf).then(response => {
const content = response.data;
if (content && this.parameter.files[0]) {
downloadFile(this.parameter.files[0].name, content);
}
});
}
},
handleRemove(file) {
let fileName = file.file.name ? file.file.name : file.name
this.$alert(this.$t('api_test.environment.csv_delete') + ':【 ' + fileName + " 】?", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this.$refs.upload.handleRemove(file);
for (let i = 0; i < this.parameter.files.length; i++) {
let paramFileName = this.parameter.files[i].name ?
this.parameter.files[i].name : this.parameter.files[i].file.name;
if (fileName === paramFileName) {
this.parameter.files.splice(i, 1);
this.$refs.upload.handleRemove(file);
break;
}
}
}
}
});
},
exceed() {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
},
upload(file) {
this.parameter.files = [];
this.parameter.files.push(file);
},
uploadValidate(file) {
if (this.parameter.files.length > 0) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
return false;
}
if (file.size / 1024 / 1024 > 500) {
this.$warning(this.$t('api_test.request.body_upload_limit_size'));
return false;
}
if (!file.name.endsWith(".csv")) {
this.$warning(this.$t('variables.cvs_info'));
return false;
}
return true;
},
},
created() {
if (!this.parameter.files) {
this.parameter.files = [];
}
}
}
</script>
<style scoped>
.el-upload {
background-color: black;
}
.api-body-upload >>> .el-upload {
height: 30px;
width: 32px;
}
.upload-default {
min-height: 30px;
width: 32px;
line-height: 32px;
}
.el-icon-plus {
font-size: 16px;
}
.api-body-upload >>> .el-upload-list__item {
height: 30px;
width: auto;
padding: 2px 5px;
margin-bottom: 0px;
}
.api-body-upload >>> .el-upload-list--picture-card {
}
.api-body-upload {
min-height: 30px;
border: 1px solid #EBEEF5;
padding: 2px;
border-radius: 4px;
}
.ms-body-upload {
min-height: 0px;
height: 30px;
border: 0px;
padding: 0px;
border-radius: 0px;
}
.upload-item {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View File

@ -30,7 +30,7 @@
<script> <script>
import {CommonConfig} from "../../model/EnvironmentModel"; import {CommonConfig} from "../../model/EnvironmentModel";
import MsApiScenarioVariables from "../ApiScenarioVariables"; import MsApiScenarioVariables from "@/business/components/api/test/components/environment/ApiScenarioVariables";
import MsApiHostTable from "../ApiHostTable"; import MsApiHostTable from "../ApiHostTable";
export default { export default {

View File

@ -2,19 +2,27 @@
<el-main v-loading="result.loading" class="environment-edit" style="margin-left: 0px"> <el-main v-loading="result.loading" class="environment-edit" style="margin-left: 0px">
<el-form :model="environment" :rules="rules" ref="environment" label-width="80px"> <el-form :model="environment" :rules="rules" ref="environment" label-width="80px">
<el-row> <el-row>
<el-col :span="20"> <el-col :span="10" v-if="!isProject">
<el-form-item prop="name" :label="$t('api_test.environment.name')"> <el-form-item class="project-item" prop="currentProjectId" :label="$t('project.select')">
<el-input v-model="environment.name" :disabled="isReadOnly" :placeholder="this.$t('commons.input_name')" <el-select @change="handleProjectChange" v-model="environment.currentProjectId" filterable clearable
clearable/> size="small">
<el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="4" v-if="!hideButton"> <el-col :span="10">
<el-form-item prop="name" :label="$t('api_test.environment.name')">
<el-input v-model="environment.name" :disabled="isReadOnly" :placeholder="this.$t('commons.input_name')"
clearable size="small"/>
</el-form-item>
</el-col>
<el-col :span="4" v-if="!hideButton" :offset="isProject ? 10 : 0">
<div style="float: right;width: fit-content;"> <div style="float: right;width: fit-content;">
<div style="float: left; margin-right: 8px;"> <div style="float: left; margin-right: 8px;">
<slot name="other"></slot> <slot name="other"></slot>
</div> </div>
<div class="ms_btn"> <div class="ms_btn">
<el-button type="primary" @click="confirm" @keydown.enter.native.prevent> <el-button type="primary" @click="confirm" @keydown.enter.native.prevent size="small">
{{ $t('commons.confirm') }} {{ $t('commons.confirm') }}
</el-button> </el-button>
</div> </div>
@ -30,7 +38,7 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.http_config')" name="http"> <el-tab-pane :label="$t('api_test.environment.http_config')" name="http">
<ms-environment-http-config :project-id="projectId" :http-config="environment.config.httpConfig" <ms-environment-http-config :project-id="environment.projectId" :http-config="environment.config.httpConfig"
ref="httpConfig" :is-read-only="isReadOnly"/> ref="httpConfig" :is-read-only="isReadOnly"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.database_config')" name="sql"> <el-tab-pane :label="$t('api_test.environment.database_config')" name="sql">
@ -40,7 +48,7 @@
<ms-tcp-config :config="environment.config.tcpConfig" :is-read-only="isReadOnly"/> <ms-tcp-config :config="environment.config.tcpConfig" :is-read-only="isReadOnly"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('commons.ssl.config')" name="ssl"> <el-tab-pane :label="$t('commons.ssl.config')" name="ssl">
<ms-environment-s-s-l-config :project-id="projectId" :ssl-config="environment.config.sslConfig" <ms-environment-s-s-l-config :project-id="environment.projectId" :ssl-config="environment.config.sslConfig"
:is-read-only="isReadOnly"/> :is-read-only="isReadOnly"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.all_pre_script')" name="prescript"> <el-tab-pane :label="$t('api_test.definition.request.all_pre_script')" name="prescript">
@ -95,9 +103,10 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item style="margin-bottom: 0px;" <el-form-item style="margin-bottom: 0px;"
:label="$t('error_report_library.use_error_report')" :label="$t('error_report_library.use_error_report')"
prop="status"> prop="status">
<el-switch v-model="environment.config.useErrorCode" style="margin-right: 10px" :disabled="isReadOnly"/> <el-switch v-model="environment.config.useErrorCode" style="margin-right: 10px"
:disabled="isReadOnly"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -128,11 +137,7 @@
:is-show-json-path-suggest="false"/> :is-show-json-path-suggest="false"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<!-- <div class="environment-footer">-->
<!-- <ms-dialog-footer-->
<!-- @cancel="cancel"-->
<!-- @confirm="save()"/>-->
<!-- </div>-->
</el-form> </el-form>
<ms-change-history ref="changeHistory"/> <ms-change-history ref="changeHistory"/>
</el-main> </el-main>
@ -158,6 +163,7 @@ import EnvironmentGlobalScript from "@/business/components/api/test/components/e
import GlobalAssertions from "@/business/components/api/definition/components/assertion/GlobalAssertions"; import GlobalAssertions from "@/business/components/api/definition/components/assertion/GlobalAssertions";
import MsChangeHistory from "../../../../history/ChangeHistory"; import MsChangeHistory from "../../../../history/ChangeHistory";
import MsDialogHeader from "../../../../common/components/MsDialogHeader"; import MsDialogHeader from "../../../../common/components/MsDialogHeader";
import {getUploadConfig, request} from "@/common/js/ajax";
export default { export default {
name: "EnvironmentEdit", name: "EnvironmentEdit",
@ -185,6 +191,13 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
projectList: {
type: Array,
default() {
return [];
}
},
isProject: Boolean
}, },
data() { data() {
return { return {
@ -196,6 +209,9 @@ export default {
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'}, {required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 64, message: this.$t('commons.input_limit', [1, 64]), trigger: 'blur'} {max: 64, message: this.$t('commons.input_limit', [1, 64]), trigger: 'blur'}
], ],
currentProjectId: [
{required: true, message: "", trigger: 'blur'},
],
}, },
headerSuggestions: REQUEST_HEADERS, headerSuggestions: REQUEST_HEADERS,
activeName: 'common' activeName: 'common'
@ -295,6 +311,15 @@ export default {
this.isRefresh = true; this.isRefresh = true;
}); });
this.envEnable = o.enable; this.envEnable = o.enable;
},
//projectId
'environment.currentProjectId'() {
// el-select''''projectId使
if (!this.environment.currentProjectId) {
this.environment.projectId = null;
} else {
this.environment.projectId = this.environment.currentProjectId;
}
} }
}, },
computed: { computed: {
@ -361,6 +386,29 @@ export default {
} }
return uploadFiles; return uploadFiles;
}, },
getVariablesFiles(obj) {
let variablesFiles = [];
obj.variablesFilesIds = [];
// csv
if (obj.config.commonConfig.variables) {
obj.config.commonConfig.variables.forEach(param => {
if (param.type === 'CSV' && param.files) {
param.files.forEach(item => {
if (item.file && item.file.name) {
if (!item.id) {
let fileId = getUUID().substring(0, 12);
item.name = item.file.name;
item.id = fileId;
}
obj.variablesFilesIds.push(item.id);
variablesFiles.push(item.file);
}
})
}
});
}
return variablesFiles;
},
check(items) { check(items) {
let repeatKey = ""; let repeatKey = "";
items.forEach((item, index) => { items.forEach((item, index) => {
@ -373,7 +421,7 @@ export default {
return repeatKey; return repeatKey;
}, },
_save(environment) { _save(environment) {
if (!this.projectId) { if (!environment.projectId) {
this.$warning(this.$t('api_test.select_project')); this.$warning(this.$t('api_test.select_project'));
return; return;
} }
@ -401,25 +449,47 @@ export default {
} }
}) })
} }
environment.config.commonConfig.variables.forEach(variable => {
if (variable.type === 'CSV' && variable.files.length === 0) {
message = this.$t('api_test.automation.csv_warning');
return;
}
})
if (message) { if (message) {
this.$warning(message); this.$warning(message);
return; return;
} }
let bodyFiles = this.geFiles(environment); let bodyFiles = this.geFiles(environment);
let variablesFiles = this.getVariablesFiles(environment);
let formData = new FormData();
if (bodyFiles) {
bodyFiles.forEach(f => {
formData.append("files", f);
})
}
if (variablesFiles) {
variablesFiles.forEach(f => {
formData.append("variablesFiles", f);
})
}
let param = this.buildParam(environment); let param = this.buildParam(environment);
let url = '/api/environment/add'; let url = '/api/environment/add';
if (param.id) { if (param.id) {
url = '/api/environment/update'; url = '/api/environment/update';
} }
this.$fileUpload(url, null, bodyFiles, param, response => { formData.append('request', new Blob([JSON.stringify(param)], {type: "application/json"}));
//this.result = this.$post(url, param, response => { let axiosRequestConfig = getUploadConfig(url, formData);
if (!param.id) { request(axiosRequestConfig, (response) => {
environment.id = response.data; if (response.success) {
this.$success(this.$t('commons.save_success'));
this.$emit('refreshAfterSave'); //EnvironmentList.vue使
this.cancel()
} }
this.$success(this.$t('commons.save_success')); }, error => {
this.$emit('refreshAfterSave'); //EnvironmentList.vue使 this.$emit('errorRefresh', error);
this.cancel();
}); });
}, },
buildParam: function (environment) { buildParam: function (environment) {
let param = {}; let param = {};
@ -447,6 +517,9 @@ export default {
clearValidate() { clearValidate() {
this.$refs["environment"].clearValidate(); this.$refs["environment"].clearValidate();
}, },
handleProjectChange() { //,
this.environment.config.httpConfig.conditions = [];
},
}, },
} }
</script> </script>
@ -468,10 +541,11 @@ span:not(:first-child) {
margin-top: 15px; margin-top: 15px;
} }
.errorReportConfigSwitch /deep/ .el-switch__label{ .errorReportConfigSwitch /deep/ .el-switch__label {
color: #D8DAE2; color: #D8DAE2;
} }
.errorReportConfigSwitch /deep/ .is-active{
.errorReportConfigSwitch /deep/ .is-active {
color: var(--count_number); color: var(--count_number);
} }
</style> </style>

View File

@ -6,7 +6,8 @@
<el-row type="flex" justify="space-between"> <el-row type="flex" justify="space-between">
<el-col :span="14"> <el-col :span="14">
<span class="ms-env-span" style="line-height: 30px;">{{ $t('api_test.environment.socket') }}</span> <span class="ms-env-span" style="line-height: 30px;">{{ $t('api_test.environment.socket') }}</span>
<el-input v-model="condition.socket" style="width: 85%" :placeholder="$t('api_test.request.url_description')" clearable size="small"> <el-input v-model="condition.socket" style="width: 85%"
:placeholder="$t('api_test.request.url_description')" clearable size="small">
<template slot="prepend"> <template slot="prepend">
<el-select v-model="condition.protocol" class="request-protocol-select" size="small"> <el-select v-model="condition.protocol" class="request-protocol-select" size="small">
<el-option label="http://" value="http"/> <el-option label="http://" value="http"/>
@ -17,7 +18,8 @@
</el-col> </el-col>
<el-col :span="10"> <el-col :span="10">
<span style="margin-right: 12px; line-height: 30px;">{{ $t('commons.description') }}</span> <span style="margin-right: 12px; line-height: 30px;">{{ $t('commons.description') }}</span>
<el-input v-model="condition.description" maxlength="200" :show-word-limit="true" size="small" style="width: 70%;"/> <el-input v-model="condition.description" maxlength="200" :show-word-limit="true" size="small"
style="width: 70%;"/>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@ -29,7 +31,8 @@
<el-radio label="PATH">{{ $t('api_test.definition.api_path') }}</el-radio> <el-radio label="PATH">{{ $t('api_test.definition.api_path') }}</el-radio>
</el-radio-group> </el-radio-group>
<div v-if="condition.type === 'MODULE'" style="margin-top: 6px"> <div v-if="condition.type === 'MODULE'" style="margin-top: 6px">
<ms-select-tree size="small" :data="moduleOptions" :default-key="condition.ids" @getValue="setModule" :obj="moduleObj" clearable :checkStrictly="true" multiple v-if="!loading"/> <ms-select-tree size="small" :data="moduleOptions" :default-key="condition.ids" @getValue="setModule"
:obj="moduleObj" clearable :checkStrictly="true" multiple v-if="!loading"/>
</div> </div>
<div v-if="condition.type === 'PATH'" style="margin-top: 6px"> <div v-if="condition.type === 'PATH'" style="margin-top: 6px">
<el-input v-model="pathDetails.name" :placeholder="$t('api_test.value')" clearable size="small"> <el-input v-model="pathDetails.name" :placeholder="$t('api_test.value')" clearable size="small">
@ -70,7 +73,8 @@
{{ getUrl(row) }} {{ getUrl(row) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="type" :label="$t('api_test.environment.condition_enable')" show-overflow-tooltip min-width="100px"> <el-table-column prop="type" :label="$t('api_test.environment.condition_enable')" show-overflow-tooltip
min-width="100px">
<template v-slot:default="{row}"> <template v-slot:default="{row}">
{{ getName(row) }} {{ getName(row) }}
</template> </template>
@ -153,7 +157,15 @@ export default {
}, },
loading: false, loading: false,
pathDetails: new KeyValue({name: "", value: "contains"}), pathDetails: new KeyValue({name: "", value: "contains"}),
condition: {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "http", socket: "", domain: "", port: 0, headers: [new KeyValue()]}, condition: {
type: "NONE",
details: [new KeyValue({name: "", value: "contains"})],
protocol: "http",
socket: "",
domain: "",
port: 0,
headers: [new KeyValue()]
},
beforeCondition: {} beforeCondition: {}
}; };
}, },
@ -174,7 +186,16 @@ export default {
this.condition.description = this.httpConfig.description; this.condition.description = this.httpConfig.description;
this.add(); this.add();
} }
this.condition = {id: undefined, type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "http", socket: "", domain: "", port: 0, headers: [new KeyValue()]}; this.condition = {
id: undefined,
type: "NONE",
details: [new KeyValue({name: "", value: "contains"})],
protocol: "http",
socket: "",
domain: "",
port: 0,
headers: [new KeyValue()]
};
}, },
}, },
methods: { methods: {
@ -216,7 +237,15 @@ export default {
} }
}, },
selectRow(row) { selectRow(row) {
this.condition = {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "http", socket: "", domain: "", port: 0, headers: [new KeyValue()]}; this.condition = {
type: "NONE",
details: [new KeyValue({name: "", value: "contains"})],
protocol: "http",
socket: "",
domain: "",
port: 0,
headers: [new KeyValue()]
};
if (row) { if (row) {
this.httpConfig.socket = row.socket; this.httpConfig.socket = row.socket;
this.httpConfig.protocol = row.protocol; this.httpConfig.protocol = row.protocol;
@ -309,7 +338,14 @@ export default {
this.$refs.envTable.setCurrentRow(0); this.$refs.envTable.setCurrentRow(0);
}, },
clear() { clear() {
this.condition = {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "http", socket: "", domain: "", headers: [new KeyValue()]}; this.condition = {
type: "NONE",
details: [new KeyValue({name: "", value: "contains"})],
protocol: "http",
socket: "",
domain: "",
headers: [new KeyValue()]
};
this.$refs.envTable.setCurrentRow(0); this.$refs.envTable.setCurrentRow(0);
}, },
reload() { reload() {
@ -336,8 +372,15 @@ export default {
} }
this.validateSocket(); this.validateSocket();
let obj = { let obj = {
id: getUUID(), type: this.condition.type, socket: this.condition.socket, protocol: this.condition.protocol, headers: this.condition.headers, id: getUUID(),
domain: this.condition.domain, port: this.condition.port, time: new Date().getTime(), description: this.condition.description type: this.condition.type,
socket: this.condition.socket,
protocol: this.condition.protocol,
headers: this.condition.headers,
domain: this.condition.domain,
port: this.condition.port,
time: new Date().getTime(),
description: this.condition.description
}; };
if (this.condition.type === "PATH") { if (this.condition.type === "PATH") {
obj.details = [JSON.parse(JSON.stringify(this.pathDetails))]; obj.details = [JSON.parse(JSON.stringify(this.pathDetails))];
@ -358,7 +401,16 @@ export default {
return; return;
} }
const index = this.httpConfig.conditions.findIndex((d) => d.id === row.id); const index = this.httpConfig.conditions.findIndex((d) => d.id === row.id);
let obj = {id: getUUID(), type: row.type, socket: row.socket, details: row.details, protocol: row.protocol, headers: JSON.parse(JSON.stringify(row.headers)), domain: row.domain, time: new Date().getTime()}; let obj = {
id: getUUID(),
type: row.type,
socket: row.socket,
details: row.details,
protocol: row.protocol,
headers: JSON.parse(JSON.stringify(row.headers)),
domain: row.domain,
time: new Date().getTime()
};
if (index != -1) { if (index != -1) {
this.httpConfig.conditions.splice(index, 0, obj); this.httpConfig.conditions.splice(index, 0, obj);
} else { } else {
@ -402,7 +454,7 @@ export default {
let line = item.split(/|:/); let line = item.split(/|:/);
let values = item.split(line[0] + ":"); let values = item.split(line[0] + ":");
let required = false; let required = false;
keyValues.unshift(new KeyValue({ keyValues.push(new KeyValue({
name: line[0], name: line[0],
required: required, required: required,
value: values[1], value: values[1],
@ -429,7 +481,7 @@ export default {
} }
} }
if (isAdd) { if (isAdd) {
this.condition.headers.unshift(keyValue); this.condition.headers.splice(this.condition.headers.indexOf(h => !h.name), 0, keyValue);
} }
}) })
} }

View File

@ -0,0 +1,162 @@
<template>
<el-dialog :visible="dialogVisible" :title="dialogTitle"
@close="close" :close-on-click-modal="false" append-to-body
width="35%">
<el-form>
<el-form :rules="rules" label-width="80px" v-model="modeId">
<el-form-item prop="modeId" :label="$t('commons.import_mode')">
<el-select size="small" v-model="modeId">
<el-option v-for="item in modeOptions" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item>
<el-upload
class="api-upload" drag action="alert"
:on-change="handleFileChange"
:limit="1" :file-list="uploadFiles"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:auto-upload="false" accept=".json">
<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') }}
{{ '' + $t('api_test.api_import.ms_env_import_file_limit') }}
</div>
</el-upload>
</el-form-item>
</el-form>
</el-form>
<template v-slot:footer>
<el-button type="primary" @click="save">
{{ $t('commons.confirm') }}
</el-button>
</template>
</el-dialog>
</template>
<script>
export default {
name: "VariableImport",
props: {
projectList: {
type: Array,
default() {
return [];
}
},
toImportProjectId: {
type: String,
default() {
return "";
}
}
},
data() {
return {
currentProjectId: '', //id
uploadFiles: [],
dialogTitle: this.$t('commons.import_variable'),
dialogVisible: false,
modeOptions: [
{
id: 'fullCoverage',
name: this.$t('commons.cover')
},
{
id: 'incrementalMerge',
name: this.$t('commons.not_cover')
}
],
modeId: 'fullCoverage',
rules: {
modeId: [
{required: true, message: "", trigger: 'blur'},
],
},
}
},
watch: {
//
dialogVisible(val, oldVal) {
if (oldVal === false) {
this.currentProjectId = '';
this.uploadFiles = [];
}
}
},
methods: {
handleFileChange(file, uploadFiles) {
this.uploadFiles = uploadFiles;
},
save() {
if (this.uploadFiles.length > 0) {
for (let i = 0; i < this.uploadFiles.length; i++) {
this.uploadValidate(this.uploadFiles[i]);
let file = this.uploadFiles[i];
if (!file) {
continue;
}
let reader = new FileReader();
reader.readAsText(file.raw)
reader.onload = (e) => {
let fileString = e.target.result;
let messages = '';
try {
JSON.parse(fileString).map(env => {
if (!env.name) {
messages = this.$t('api_test.automation.variable_warning')
}
})
if (messages !== '') {
this.$warning(messages);
return;
}
this.$emit("mergeData", fileString, this.modeId);
this.dialogVisible = false;
this.$success(this.$t('commons.save_success'));
} catch (exception) {
this.$warning(this.$t('api_test.api_import.ms_env_import_file_limit'));
}
}
}
} else {
this.$warning(this.$t('test_track.case.import.import_file_tips'));
}
},
handleExceed() {
this.$warning(this.$t('api_test.api_import.file_exceed_limit'));
},
handleRemove() {
},
uploadValidate(file) { //.json20M
const extension = file.name.substring(file.name.lastIndexOf('.') + 1);
if (!(extension === 'json')) {
this.$warning(this.$t('api_test.api_import.ms_env_import_file_limit'));
}
if (file.size / 1024 / 1024 > 20) {
this.$warning(this.$t('api_test.api_import.file_size_limit'));
}
},
open() {
this.dialogVisible = true;
},
close() {
this.dialogVisible = false;
}
},
}
</script>
<style scoped>
.project-item {
padding-left: 20px;
padding-right: 20px;
}
</style>

View File

@ -2,7 +2,12 @@
<div class="msDialogHeader"> <div class="msDialogHeader">
<span style="float: left;font-size: 18px;color: #303133;">{{ title }}</span> <span style="float: left;font-size: 18px;color: #303133;">{{ title }}</span>
<div style="float: right; margin-right: 30px">
<el-tooltip effect="dark" :content="$t('commons.full_screen_editing')"
placement="top-start">
<font-awesome-icon class="alt-ico" :icon="['fa', 'expand-alt']" size="lg" @click="fullScreen"/>
</el-tooltip>
</div>
<div v-if="!hideButton" style="float: right;width: fit-content;"> <div v-if="!hideButton" style="float: right;width: fit-content;">
<div style="float: left; margin-right: 8px;"> <div style="float: left; margin-right: 8px;">
<slot name="other"></slot> <slot name="other"></slot>
@ -39,6 +44,9 @@ export default {
}, },
confirm() { confirm() {
this.$emit("confirm"); this.$emit("confirm");
},
fullScreen() {
this.$emit("fullScreen");
} }
} }
} }
@ -53,4 +61,9 @@ export default {
.msDialogHeader { .msDialogHeader {
margin-bottom: 5px; margin-bottom: 5px;
} }
.alt-ico {
font-size: 13px;
color: #8c939d;
}
</style> </style>

View File

@ -32,7 +32,9 @@
<el-table-column :label="$t('api_test.environment.socket')" show-overflow-tooltip> <el-table-column :label="$t('api_test.environment.socket')" show-overflow-tooltip>
<template v-slot="scope"> <template v-slot="scope">
<span v-if="parseDomainName(scope.row)!='SHOW_INFO'">{{ parseDomainName(scope.row) }}</span> <span v-if="parseDomainName(scope.row)!='SHOW_INFO'">{{ parseDomainName(scope.row) }}</span>
<el-button size="mini" icon="el-icon-s-data" @click="showInfo(scope.row)" v-else>{{ $t('workspace.env_group.view_details') }}</el-button> <el-button size="mini" icon="el-icon-s-data" @click="showInfo(scope.row)" v-else>
{{ $t('workspace.env_group.view_details') }}
</el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.operating')"> <el-table-column :label="$t('commons.operating')">
@ -57,18 +59,20 @@
</el-card> </el-card>
<!-- 创建编辑复制环境时的对话框 --> <!-- 创建编辑复制环境时的对话框 -->
<el-dialog :visible.sync="dialogVisible" :close-on-click-modal="false" width="66%" top="50px"> <el-dialog :visible.sync="dialogVisible" :close-on-click-modal="false" width="66%" top="50px"
:fullscreen="isFullScreen">
<template #title> <template #title>
<ms-dialog-header :title="dialogTitle" :hide-button="true" <ms-dialog-header :title="dialogTitle" :hide-button="true"
@cancel="dialogVisible = false" @cancel="dialogVisible = false"
@confirm="save"/> @confirm="save" @fullScreen="fullScreen"/>
</template> </template>
<environment-edit :if-create="ifCreate" :environment="currentEnvironment" ref="environmentEdit" @close="close" <environment-edit :if-create="ifCreate" :environment="currentEnvironment" ref="environmentEdit" @close="close"
@confirm="save" @confirm="save" :is-project="true"
:project-id="currentProjectId" @refreshAfterSave="refresh"> :project-list="projectList" @refreshAfterSave="refresh">
</environment-edit> </environment-edit>
</el-dialog> </el-dialog>
<environment-import :project-list="projectList" :to-import-project-id="currentProjectId" @refresh="refresh" ref="envImport"></environment-import> <environment-import :project-list="projectList" :to-import-project-id="currentProjectId" @refresh="refresh"
ref="envImport"></environment-import>
<el-dialog title="域名列表" :visible.sync="domainVisible"> <el-dialog title="域名列表" :visible.sync="domainVisible">
<el-table :data="conditions"> <el-table :data="conditions">
@ -94,12 +98,14 @@
{{ row.conditionType ? "-" : getDetails(row) }} {{ row.conditionType ? "-" : getDetails(row) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="description" show-overflow-tooltip min-width="120px" :label="$t('commons.description')"> <el-table-column prop="description" show-overflow-tooltip min-width="120px"
:label="$t('commons.description')">
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<span>{{ row.description ? row.description : "-" }}</span> <span>{{ row.description ? row.description : "-" }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="createTime" show-overflow-tooltip min-width="120px" :label="$t('commons.create_time')"> <el-table-column prop="createTime" show-overflow-tooltip min-width="120px"
:label="$t('commons.create_time')">
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<span v-if="!row.conditionType">{{ row.time | timestampFormatDate }}</span> <span v-if="!row.conditionType">{{ row.time | timestampFormatDate }}</span>
<span v-else>-</span> <span v-else>-</span>
@ -170,6 +176,7 @@ export default {
projectFilters: [], projectFilters: [],
screenHeight: 'calc(100vh - 155px)', screenHeight: 'calc(100vh - 155px)',
ifCreate: false, // ifCreate: false, //
isFullScreen: false //
} }
}, },
created() { created() {
@ -180,6 +187,9 @@ export default {
}, },
methods: { methods: {
fullScreen() {
this.isFullScreen = !this.isFullScreen;
},
showInfo(row) { showInfo(row) {
const config = JSON.parse(row.config); const config = JSON.parse(row.config);
this.conditions = config.httpConfig.conditions; this.conditions = config.httpConfig.conditions;
@ -193,7 +203,7 @@ export default {
} }
this.domainVisible = true; this.domainVisible = true;
}, },
save(){ save() {
this.$refs.environmentEdit.save(); this.$refs.environmentEdit.save();
}, },
getName(row) { getName(row) {
@ -387,8 +397,7 @@ export default {
} else { //config,protocoldomain } else { //config,protocoldomain
return environment.protocol + '://' + environment.domain; return environment.protocol + '://' + environment.domain;
} }
} },
}, },

View File

@ -38,7 +38,9 @@
<el-table-column :label="$t('api_test.environment.socket')" show-overflow-tooltip> <el-table-column :label="$t('api_test.environment.socket')" show-overflow-tooltip>
<template v-slot="scope"> <template v-slot="scope">
<span v-if="parseDomainName(scope.row)!='SHOW_INFO'">{{ parseDomainName(scope.row) }}</span> <span v-if="parseDomainName(scope.row)!='SHOW_INFO'">{{ parseDomainName(scope.row) }}</span>
<el-button size="mini" icon="el-icon-s-data" @click="showInfo(scope.row)" v-else>{{ $t('workspace.env_group.view_details') }}</el-button> <el-button size="mini" icon="el-icon-s-data" @click="showInfo(scope.row)" v-else>
{{ $t('workspace.env_group.view_details') }}
</el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.operating')"> <el-table-column :label="$t('commons.operating')">
@ -62,26 +64,16 @@
</el-card> </el-card>
<!-- 创建编辑复制环境时的对话框 --> <!-- 创建编辑复制环境时的对话框 -->
<el-dialog :visible.sync="dialogVisible" :close-on-click-modal="false" top="50px" width="66%"> <el-dialog :visible.sync="dialogVisible" :close-on-click-modal="false" top="50px" width="66%"
:fullscreen="isFullScreen">
<template #title> <template #title>
<ms-dialog-header :title="dialogTitle" <ms-dialog-header :title="dialogTitle"
@cancel="dialogVisible = false" @cancel="dialogVisible = false" :hide-button="true"
@confirm="save"/> @confirm="save" @fullScreen="fullScreen"/>
</template> </template>
<el-row>
<el-col :span="20">
<el-form label-width="80px" :rules="rules" style="display: flow-root">
<el-form-item class="project-item" prop="currentProjectId" :label="$t('project.select')">
<el-select @change="handleProjectChange" v-model="currentProjectId" filterable clearable>
<el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-form>
</el-col>
</el-row>
<environment-edit :if-create="ifCreate" :environment="currentEnvironment" ref="environmentEdit" @close="close" <environment-edit :if-create="ifCreate" :environment="currentEnvironment" ref="environmentEdit" @close="close"
:hide-button="true" :project-list="projectList" @confirm="save" :is-project="false"
:project-id="currentProjectId" @refreshAfterSave="refresh"> @refreshAfterSave="refresh">
</environment-edit> </environment-edit>
</el-dialog> </el-dialog>
<environment-import :project-list="projectList" @refresh="refresh" ref="envImport"></environment-import> <environment-import :project-list="projectList" @refresh="refresh" ref="envImport"></environment-import>
@ -203,6 +195,7 @@ export default {
}, },
], ],
ifCreate: false, // ifCreate: false, //
isFullScreen: false
}; };
}, },
created() { created() {
@ -227,6 +220,9 @@ export default {
}, },
methods: { methods: {
fullScreen() {
this.isFullScreen = !this.isFullScreen;
},
showInfo(row) { showInfo(row) {
const config = JSON.parse(row.config); const config = JSON.parse(row.config);
this.conditions = config.httpConfig.conditions; this.conditions = config.httpConfig.conditions;
@ -316,6 +312,7 @@ export default {
editEnv(environment) { editEnv(environment) {
this.dialogTitle = this.$t('api_test.environment.config_environment'); this.dialogTitle = this.$t('api_test.environment.config_environment');
this.currentProjectId = environment.projectId; this.currentProjectId = environment.projectId;
environment.currentProjectId = environment.projectId;
const temEnv = {}; const temEnv = {};
Object.assign(temEnv, environment); Object.assign(temEnv, environment);
parseEnvironment(temEnv); //parseEnvironment parseEnvironment(temEnv); //parseEnvironment
@ -323,11 +320,12 @@ export default {
this.dialogVisible = true; this.dialogVisible = true;
this.ifCreate = false; this.ifCreate = false;
}, },
save(){ save() {
this.$refs.environmentEdit.save(); this.$refs.environmentEdit.save();
}, },
copyEnv(environment) { copyEnv(environment) {
this.currentProjectId = environment.projectId; // this.currentProjectId = environment.projectId; //
environment.currentProjectId = environment.projectId;
this.dialogTitle = this.$t('api_test.environment.copy_environment'); this.dialogTitle = this.$t('api_test.environment.copy_environment');
const temEnv = {}; const temEnv = {};
Object.assign(temEnv, environment); Object.assign(temEnv, environment);
@ -458,7 +456,7 @@ export default {
this.selectRow.forEach(row => { this.selectRow.forEach(row => {
map.set(row.projectId, row.id); map.set(row.projectId, row.id);
}) })
this.$post("/environment/group/batch/add", {map: strMapToObj(map), groupIds:value}, () => { this.$post("/environment/group/batch/add", {map: strMapToObj(map), groupIds: value}, () => {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
this.getEnvironments(); this.getEnvironments();
}) })

View File

@ -246,6 +246,7 @@ export let CUSTOM_TABLE_HEADER = {
{id: 'name', key: '2', label: 'api_test.variable_name'}, {id: 'name', key: '2', label: 'api_test.variable_name'},
{id: 'type', key: '3', label: 'test_track.case.type'}, {id: 'type', key: '3', label: 'test_track.case.type'},
{id: 'value', key: '4', label: 'api_test.value'}, {id: 'value', key: '4', label: 'api_test.value'},
{id: 'description', key: '5', label: 'commons.description'}
], ],
//缺陷列表 //缺陷列表

View File

@ -211,6 +211,8 @@ export default {
modifier: 'Modifier', modifier: 'Modifier',
validate: "Validate", validate: "Validate",
batch_add: "Batch add", batch_add: "Batch add",
import_variable: "Import variable",
export_variable: "Export variable",
batch_restore: "Batch restore", batch_restore: "Batch restore",
batch_gc: "Batch gc", batch_gc: "Batch gc",
check_project_tip: "Create or select the project first", check_project_tip: "Create or select the project first",
@ -1193,7 +1195,8 @@ export default {
copy: "Copy Test", copy: "Copy Test",
please_select_case: "Please select case", please_select_case: "Please select case",
fail_to_stop: "Fail to stop", fail_to_stop: "Fail to stop",
batch_add_parameter: "Format: parameter name: parameter value <br/> likeAccept-Encoding:utf-8", batch_add_parameter: "Format: parameter name: parameter value <br/> likeAccept-Encoding:utf-8 <br/> Note: The parameter names in batch addition are repeated, and the last data is the latest data by default",
params_format_warning: "Incorrect data format at line {0}",
create_performance_test_tips: 'This operation cannot be completed without permission to create performance tests', create_performance_test_tips: 'This operation cannot be completed without permission to create performance tests',
jar_config: { jar_config: {
title: "Upload jar package", title: "Upload jar package",
@ -1510,9 +1513,15 @@ export default {
environment_json: "Environment Config", environment_json: "Environment Config",
environment_group_id: "Environment Group ID", environment_group_id: "Environment Group ID",
select_environment: "Please select environment", select_environment: "Please select environment",
select_variable: "Please select variable",
please_save_test: "Please Save Test First", please_save_test: "Please Save Test First",
common_config: "Common Config", common_config: "Common Config",
http_config: "HTTP Config", http_config: "HTTP Config",
advanced_setting: "Click Advanced settings to add variable values",
variables_delete_info: "Are you sure you want to delete the selected variable",
csv_delete: "Are you sure you want to delete the CSV file",
delete_info: "Please select a data deletion",
list_info: "List data is separated by ,",
database_config: "Database Config", database_config: "Database Config",
tcp_config: "TCP Config", tcp_config: "TCP Config",
import: "Import Environment", import: "Import Environment",
@ -2792,6 +2801,7 @@ export default {
delimiter: "Delimiter", delimiter: "Delimiter",
format: "Output format", format: "Output format",
quoted_data: "Whether to allow quotes", quoted_data: "Whether to allow quotes",
csv_download: "CSV file does not support exporting"
}, },
auth_source: { auth_source: {
delete_prompt: 'This operation will delete the authentication source, do you want to continue? ', delete_prompt: 'This operation will delete the authentication source, do you want to continue? ',

View File

@ -212,6 +212,8 @@ export default {
modifier: '修改人', modifier: '修改人',
validate: "校验", validate: "校验",
batch_add: "批量添加", batch_add: "批量添加",
import_variable: "导入变量",
export_variable: "导出变量",
batch_restore: "批量恢复", batch_restore: "批量恢复",
batch_gc: "批量回收", batch_gc: "批量回收",
check_project_tip: "请先创建或选择项目", check_project_tip: "请先创建或选择项目",
@ -1203,7 +1205,8 @@ export default {
copy: "复制测试", copy: "复制测试",
please_select_case: "请选择用例", please_select_case: "请选择用例",
fail_to_stop: "失败停止", fail_to_stop: "失败停止",
batch_add_parameter: "格式:参数名:参数值 <br/> 如Accept-Encoding:utf-8", batch_add_parameter: "格式:参数名:参数值 <br/> 如Accept-Encoding:utf-8 <br/> 注:批量添加里的参数名重复,默认以最后一条数据为最新数据",
params_format_warning: "第{0}行数据格式有误",
create_performance_test_tips: '没有创建性能测试的权限,无法完成此操作', create_performance_test_tips: '没有创建性能测试的权限,无法完成此操作',
jar_config: { jar_config: {
title: "上传jar包", title: "上传jar包",
@ -1519,8 +1522,14 @@ export default {
environment_json: "环境配置", environment_json: "环境配置",
environment_group_id: "环境组ID", environment_group_id: "环境组ID",
select_environment: "请选择环境", select_environment: "请选择环境",
select_variable: "请选择变量",
please_save_test: "请先保存测试", please_save_test: "请先保存测试",
common_config: "通用配置", common_config: "通用配置",
list_info: "列表数据用,分隔",
advanced_setting: "点击高级设置,添加变量值",
variables_delete_info: "是否确认删除所选变量",
csv_delete: "是否确认删除CSV文件",
delete_info: "请选择一条数据删除",
http_config: "HTTP配置", http_config: "HTTP配置",
database_config: "数据库配置", database_config: "数据库配置",
tcp_config: "TCP配置", tcp_config: "TCP配置",
@ -2801,6 +2810,7 @@ export default {
delimiter: "分隔符", delimiter: "分隔符",
format: "输出格式", format: "输出格式",
quoted_data: "是否允许带引号", quoted_data: "是否允许带引号",
csv_download: "CSV文件暂不支持导出"
}, },
auth_source: { auth_source: {
delete_prompt: '此操作会删除认证源,是否继续?', delete_prompt: '此操作会删除认证源,是否继续?',

View File

@ -212,6 +212,8 @@ export default {
modifier: '修改人', modifier: '修改人',
validate: "校驗", validate: "校驗",
batch_add: "批量添加", batch_add: "批量添加",
import_variable: "導出變量",
export_variable: "导出变量",
batch_restore: "批量恢復", batch_restore: "批量恢復",
batch_gc: "批量回收", batch_gc: "批量回收",
check_project_tip: "請先創建或選擇項目", check_project_tip: "請先創建或選擇項目",
@ -1200,7 +1202,8 @@ export default {
copy: "復製測試", copy: "復製測試",
please_select_case: "請選擇用例", please_select_case: "請選擇用例",
fail_to_stop: "失敗停止", fail_to_stop: "失敗停止",
batch_add_parameter: "格式:參數名:參數值 <br/> 如Accept-Encoding:utf-8", batch_add_parameter: "格式:參數名:參數值 <br/> 如Accept-Encoding:utf-8 <br/> 注:批量添加里的參數名重複,默認以最後一條數據為最新數據",
params_format_warning: "第{0}行數據格式有誤",
create_performance_test_tips: '沒有創建性能測試的權限,無法完成此操作', create_performance_test_tips: '沒有創建性能測試的權限,無法完成此操作',
jar_config: { jar_config: {
title: "上傳jar包", title: "上傳jar包",
@ -1516,9 +1519,15 @@ export default {
environment_json: "環境配置", environment_json: "環境配置",
environment_group_id: "環境組ID", environment_group_id: "環境組ID",
select_environment: "請選擇環境", select_environment: "請選擇環境",
select_variable: "請選擇变量",
please_save_test: "請先保存測試", please_save_test: "請先保存測試",
common_config: "通用配置", common_config: "通用配置",
http_config: "HTTP配置", http_config: "HTTP配置",
list_info: "清單數據用,分隔",
advanced_setting: "點擊高級設定,添加變數值",
variables_delete_info: "是否確認刪除所選變量",
csv_delete: "是否確認删除CSV檔案",
delete_info: "請選擇一條數據刪除",
database_config: "數據庫配置", database_config: "數據庫配置",
tcp_config: "TCP配置", tcp_config: "TCP配置",
import: "導入環境", import: "導入環境",
@ -2795,6 +2804,7 @@ export default {
delimiter: "分隔符", delimiter: "分隔符",
format: "輸出格式", format: "輸出格式",
quoted_data: "是否允許帶引號", quoted_data: "是否允許帶引號",
csv_download: "CSV文件暫不支持導出"
}, },
auth_source: { auth_source: {
delete_prompt: '此操作會刪除認證源,是否繼續?', delete_prompt: '此操作會刪除認證源,是否繼續?',