This commit is contained in:
shiziyuan9527 2021-01-07 10:22:59 +08:00
commit 87a074310f
16 changed files with 521 additions and 48 deletions

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto;
import io.metersphere.api.dto.scenario.DatabaseConfig;
import lombok.Data;
@Data
public class EnvironmentDTO {
private String environmentId;
private DatabaseConfig databaseConfig;
}

View File

@ -11,6 +11,7 @@ import io.metersphere.api.dto.definition.request.assertions.MsAssertions;
import io.metersphere.api.dto.definition.request.auth.MsAuthManager; import io.metersphere.api.dto.definition.request.auth.MsAuthManager;
import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager; import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager;
import io.metersphere.api.dto.definition.request.controller.MsIfController; import io.metersphere.api.dto.definition.request.controller.MsIfController;
import io.metersphere.api.dto.definition.request.controller.MsLoopController;
import io.metersphere.api.dto.definition.request.extract.MsExtract; import io.metersphere.api.dto.definition.request.extract.MsExtract;
import io.metersphere.api.dto.definition.request.processors.MsJSR223Processor; import io.metersphere.api.dto.definition.request.processors.MsJSR223Processor;
import io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor; import io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor;
@ -56,11 +57,12 @@ import java.util.List;
@JsonSubTypes.Type(value = MsConstantTimer.class, name = "ConstantTimer"), @JsonSubTypes.Type(value = MsConstantTimer.class, name = "ConstantTimer"),
@JsonSubTypes.Type(value = MsIfController.class, name = "IfController"), @JsonSubTypes.Type(value = MsIfController.class, name = "IfController"),
@JsonSubTypes.Type(value = MsScenario.class, name = "scenario"), @JsonSubTypes.Type(value = MsScenario.class, name = "scenario"),
@JsonSubTypes.Type(value = MsLoopController.class, name = "LoopController"),
}) })
@JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223Processor.class, MsJSR223PostProcessor.class, @JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223Processor.class, MsJSR223PostProcessor.class,
MsJSR223PreProcessor.class, MsTestPlan.class, MsThreadGroup.class, AuthManager.class, MsAssertions.class, MsJSR223PreProcessor.class, MsTestPlan.class, MsThreadGroup.class, AuthManager.class, MsAssertions.class,
MsExtract.class, MsTCPSampler.class, MsDubboSampler.class, MsJDBCSampler.class, MsConstantTimer.class, MsIfController.class, MsScenario.class}, typeKey = "type") MsExtract.class, MsTCPSampler.class, MsDubboSampler.class, MsJDBCSampler.class, MsConstantTimer.class, MsIfController.class, MsScenario.class, MsLoopController.class}, typeKey = "type")
@Data @Data
public abstract class MsTestElement { public abstract class MsTestElement {
private String type; private String type;

View File

@ -0,0 +1,126 @@
package io.metersphere.api.dto.definition.request.controller;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.definition.request.controller.loop.CountController;
import io.metersphere.api.dto.definition.request.controller.loop.MsForEachController;
import io.metersphere.api.dto.definition.request.controller.loop.MsWhileController;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.control.ForeachController;
import org.apache.jmeter.control.GenericController;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.control.WhileController;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "LoopController")
public class MsLoopController extends MsTestElement {
private String type = "LoopController";
@JSONField(ordinal = 20)
private String loopType;
@JSONField(ordinal = 21)
private CountController countController;
@JSONField(ordinal = 22)
private MsForEachController forEachController;
@JSONField(ordinal = 23)
private MsWhileController whileController;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
if (!this.isEnable()) {
return;
}
GenericController controller = controller();
if (controller == null)
return;
final HashTree groupTree = tree.add(controller);
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(groupTree, el.getHashTree(), config);
});
}
}
private LoopController loopController() {
LoopController loopController = new LoopController();
loopController.setEnabled(true);
loopController.setName(this.getLabel());
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("LoopControlPanel"));
loopController.setContinueForever(countController.isProceed());
loopController.setLoops(countController.getLoops());
return loopController;
}
public String getCondition() {
String variable = "\"" + this.whileController.getVariable() + "\"";
String operator = this.whileController.getOperator();
String value = "\"" + this.whileController.getValue() + "\"";
if (StringUtils.contains(operator, "~")) {
value = "\".*" + this.whileController.getValue() + ".*\"";
}
if (StringUtils.equals(operator, "is empty")) {
variable = "empty(" + variable + ")";
operator = "";
value = "";
}
if (StringUtils.equals(operator, "is not empty")) {
variable = "!empty(" + variable + ")";
operator = "";
value = "";
}
return "${__jexl3(" + variable + operator + value + ")}";
}
private WhileController whileController() {
WhileController controller = new WhileController();
controller.setEnabled(true);
controller.setName(this.getLabel());
controller.setProperty(TestElement.TEST_CLASS, WhileController.class.getName());
controller.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("WhileControllerGui"));
controller.setCondition(getCondition());
return controller;
}
private ForeachController foreachController() {
ForeachController controller = new ForeachController();
controller.setEnabled(true);
controller.setName(this.getLabel());
controller.setProperty(TestElement.TEST_CLASS, ForeachController.class.getName());
controller.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ForeachControlPanel"));
controller.setInputVal(this.forEachController.getInputVal());
controller.setReturnVal(this.forEachController.getReturnVal());
controller.setUseSeparator(true);
return controller;
}
private GenericController controller() {
if (StringUtils.equals(this.loopType, "WHILE") && this.whileController != null) {
return whileController();
}
if (StringUtils.equals(this.loopType, "FOREACH") && this.forEachController != null) {
return foreachController();
}
if (StringUtils.equals(this.loopType, "LOOP_COUNT") && this.countController != null) {
return loopController();
}
return null;
}
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.definition.request.controller.loop;
import lombok.Data;
@Data
public class CountController {
private int loops;
private int interval;
private boolean proceed;
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.definition.request.controller.loop;
import lombok.Data;
@Data
public class MsForEachController {
private String inputVal;
private String returnVal;
private String interval;
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto.definition.request.controller.loop;
import lombok.Data;
@Data
public class MsWhileController {
private String variable;
private String operator;
private String value;
private int timeout;
}

View File

@ -325,7 +325,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
requestResult.addPassAssertions(); requestResult.addPassAssertions();
} }
//xpath 提取错误会添加断言错误 //xpath 提取错误会添加断言错误
if (!responseAssertionResult.getMessage().contains("The required item type of the first operand of")) { if (StringUtils.isBlank(responseAssertionResult.getMessage()) || !responseAssertionResult.getMessage().contains("The required item type of the first operand of")) {
responseResult.getAssertions().add(responseAssertionResult); responseResult.getAssertions().add(responseAssertionResult);
} }
} }

View File

@ -333,6 +333,9 @@ public class Swagger3Parser extends SwaggerAbstractParser {
} }
private Object parseSchemaProperties(Schema schema, Set<String> refSet, Map<String, Schema> infoMap) { private Object parseSchemaProperties(Schema schema, Set<String> refSet, Map<String, Schema> infoMap) {
if (schema == null) {
return null;
}
Map<String, Schema> properties = schema.getProperties(); Map<String, Schema> properties = schema.getProperties();
if (MapUtils.isEmpty(properties)) { if (MapUtils.isEmpty(properties)) {
return null; return null;

View File

@ -1,6 +1,8 @@
package io.metersphere.api.service; package io.metersphere.api.service;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.EnvironmentDTO;
import io.metersphere.api.dto.SaveHistoricalDataUpgrade; import io.metersphere.api.dto.SaveHistoricalDataUpgrade;
import io.metersphere.api.dto.automation.ScenarioStatus; import io.metersphere.api.dto.automation.ScenarioStatus;
import io.metersphere.api.dto.definition.request.MsScenario; import io.metersphere.api.dto.definition.request.MsScenario;
@ -17,6 +19,7 @@ import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
import io.metersphere.api.dto.definition.request.timer.MsConstantTimer; import io.metersphere.api.dto.definition.request.timer.MsConstantTimer;
import io.metersphere.api.dto.scenario.Body; import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.Scenario; import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.scenario.request.*; import io.metersphere.api.dto.scenario.request.*;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiScenarioMapper; import io.metersphere.base.mapper.ApiScenarioMapper;
@ -48,6 +51,9 @@ public class HistoricalDataUpgradeService {
private ExtApiScenarioMapper extApiScenarioMapper; private ExtApiScenarioMapper extApiScenarioMapper;
@Resource @Resource
SqlSessionFactory sqlSessionFactory; SqlSessionFactory sqlSessionFactory;
@Resource
ApiTestEnvironmentService apiTestEnvironmentService;
private Map<String, EnvironmentDTO> environmentDTOMap;
private int getNextNum(String projectId) { private int getNextNum(String projectId) {
ApiScenario apiScenario = extApiScenarioMapper.getNextNum(projectId); ApiScenario apiScenario = extApiScenarioMapper.getNextNum(projectId);
@ -110,6 +116,10 @@ public class HistoricalDataUpgradeService {
BeanUtils.copyBean(element, request1); BeanUtils.copyBean(element, request1);
((MsHTTPSamplerProxy) element).setProtocol(RequestType.HTTP); ((MsHTTPSamplerProxy) element).setProtocol(RequestType.HTTP);
((MsHTTPSamplerProxy) element).setArguments(request1.getParameters()); ((MsHTTPSamplerProxy) element).setArguments(request1.getParameters());
if (StringUtils.isEmpty(element.getName())) {
element.setName(request1.getPath());
}
element.setType("HTTPSamplerProxy"); element.setType("HTTPSamplerProxy");
} }
if (request instanceof DubboRequest) { if (request instanceof DubboRequest) {
@ -121,6 +131,11 @@ public class HistoricalDataUpgradeService {
element = new MsJDBCSampler(); element = new MsJDBCSampler();
SqlRequest request1 = (SqlRequest) request; SqlRequest request1 = (SqlRequest) request;
BeanUtils.copyBean(element, request1); BeanUtils.copyBean(element, request1);
EnvironmentDTO dto = environmentDTOMap.get(request1.getDataSource());
if (dto != null) {
((MsJDBCSampler) element).setEnvironmentId(dto.getEnvironmentId());
((MsJDBCSampler) element).setDataSource(dto.getDatabaseConfig());
}
element.setType("JDBCSampler"); element.setType("JDBCSampler");
} }
if (request instanceof TCPRequest) { if (request instanceof TCPRequest) {
@ -250,6 +265,7 @@ public class HistoricalDataUpgradeService {
if (!end.exists()) { if (!end.exists()) {
end.mkdir(); end.mkdir();
} }
if (filePath != null) {
for (String temp : filePath) { for (String temp : filePath) {
//添加满足情况的条件 //添加满足情况的条件
if (new File(sourcePathDir + File.separator + temp).isFile()) { if (new File(sourcePathDir + File.separator + temp).isFile()) {
@ -258,6 +274,7 @@ public class HistoricalDataUpgradeService {
} }
} }
} }
}
private void createBodyFiles(String testId) { private void createBodyFiles(String testId) {
String dir = BODY_FILE_DIR + "/" + testId; String dir = BODY_FILE_DIR + "/" + testId;
@ -310,6 +327,9 @@ public class HistoricalDataUpgradeService {
} }
public String upgrade(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade) { public String upgrade(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade) {
// 初始化环境获取数据源
getDataSource(saveHistoricalDataUpgrade.getProjectId());
ApiTestExample example = new ApiTestExample(); ApiTestExample example = new ApiTestExample();
example.createCriteria().andIdIn(saveHistoricalDataUpgrade.getTestIds()); example.createCriteria().andIdIn(saveHistoricalDataUpgrade.getTestIds());
List<ApiTest> blobs = apiTestMapper.selectByExampleWithBLOBs(example); List<ApiTest> blobs = apiTestMapper.selectByExampleWithBLOBs(example);
@ -332,4 +352,23 @@ public class HistoricalDataUpgradeService {
sqlSession.flushStatements(); sqlSession.flushStatements();
return null; return null;
} }
private void getDataSource(String projectId) {
List<ApiTestEnvironmentWithBLOBs> environments = apiTestEnvironmentService.list(projectId);
environmentDTOMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(environments)) {
environments.forEach(environment -> {
EnvironmentConfig envConfig = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
if (CollectionUtils.isNotEmpty(envConfig.getDatabaseConfigs())) {
envConfig.getDatabaseConfigs().forEach(item -> {
EnvironmentDTO dto = new EnvironmentDTO();
dto.setDatabaseConfig(item);
dto.setEnvironmentId(environment.getId());
environmentDTOMap.put(item.getId(), dto);
});
}
});
}
}
} }

View File

@ -152,6 +152,8 @@
<ms-api-scenario-component v-if="data.type==='scenario'" :scenario="data" :node="node" @remove="remove" @copyRow="copyRow"/> <ms-api-scenario-component v-if="data.type==='scenario'" :scenario="data" :node="node" @remove="remove" @copyRow="copyRow"/>
<!--条件控制器--> <!--条件控制器-->
<ms-if-controller :controller="data" :node="node" v-if="data.type==='IfController'" @remove="remove" @copyRow="copyRow"/> <ms-if-controller :controller="data" :node="node" v-if="data.type==='IfController'" @remove="remove" @copyRow="copyRow"/>
<!--循环控制器-->
<ms-loop-controller :controller="data" :node="node" v-if="data.type==='LoopController'" @remove="remove" @copyRow="copyRow"/>
<!--等待控制器--> <!--等待控制器-->
<ms-constant-timer :timer="data" :node="node" v-if="data.type==='ConstantTimer'" @remove="remove" @copyRow="copyRow"/> <ms-constant-timer :timer="data" :node="node" v-if="data.type==='ConstantTimer'" @remove="remove" @copyRow="copyRow"/>
<!--自定义脚本--> <!--自定义脚本-->
@ -168,7 +170,8 @@
<!--提取规则--> <!--提取规则-->
<ms-api-extract @remove="remove" @copyRow="copyRow" v-if="data.type==='Extract'" customizeStyle="margin-top: 0px" :extract="data" :node="node"/> <ms-api-extract @remove="remove" @copyRow="copyRow" v-if="data.type==='Extract'" customizeStyle="margin-top: 0px" :extract="data" :node="node"/>
<!--API 导入 --> <!--API 导入 -->
<ms-api-component :request="data" :currentScenario="currentScenario" :currentEnvironmentId="currentEnvironmentId" @remove="remove" @copyRow="copyRow" v-if="data.type==='HTTPSamplerProxy'||data.type==='DubboSampler'||data.type==='JDBCSampler'||data.type==='TCPSampler'" :node="node"/> <ms-api-component :request="data" :currentScenario="currentScenario" :currentEnvironmentId="currentEnvironmentId" @remove="remove" @copyRow="copyRow"
v-if="data.type==='HTTPSamplerProxy'||data.type==='DubboSampler'||data.type==='JDBCSampler'||data.type==='TCPSampler'" :node="node"/>
</template> </template>
</span> </span>
</el-tree> </el-tree>
@ -227,7 +230,7 @@
<script> <script>
import {API_STATUS, PRIORITY} from "../../definition/model/JsonData"; import {API_STATUS, PRIORITY} from "../../definition/model/JsonData";
import {WORKSPACE_ID} from '@/common/js/constants'; import {WORKSPACE_ID} from '@/common/js/constants';
import {Assertions, Extract, IfController, JSR223Processor, ConstantTimer} from "../../definition/model/ApiTestModel"; import {Assertions, Extract, IfController, JSR223Processor, ConstantTimer, LoopController} from "../../definition/model/ApiTestModel";
import MsJsr233Processor from "./Jsr233Processor"; import MsJsr233Processor from "./Jsr233Processor";
import {parseEnvironment} from "../../definition/model/EnvironmentModel"; import {parseEnvironment} from "../../definition/model/EnvironmentModel";
import MsConstantTimer from "./ConstantTimer"; import MsConstantTimer from "./ConstantTimer";
@ -241,7 +244,7 @@
import ApiEnvironmentConfig from "../../definition/components/environment/ApiEnvironmentConfig"; import ApiEnvironmentConfig from "../../definition/components/environment/ApiEnvironmentConfig";
import MsInputTag from "./MsInputTag"; import MsInputTag from "./MsInputTag";
import MsRun from "./DebugRun"; import MsRun from "./DebugRun";
import MsImportApiScenario from "./ImportApiScenario"; import MsLoopController from "./LoopController";
import MsApiScenarioComponent from "./ApiScenarioComponent"; import MsApiScenarioComponent from "./ApiScenarioComponent";
import MsApiReportDetail from "../report/ApiReportDetail"; import MsApiReportDetail from "../report/ApiReportDetail";
import MsScenarioParameters from "./ScenarioParameters"; import MsScenarioParameters from "./ScenarioParameters";
@ -275,6 +278,7 @@
MsApiCustomize, MsApiCustomize,
ApiImport, ApiImport,
InputTag, InputTag,
MsLoopController,
}, },
data() { data() {
return { return {
@ -387,6 +391,16 @@
this.addComponent('IfController') this.addComponent('IfController')
} }
}, },
{
title: this.$t('api_test.automation.loop_controller'),
show: this.showButton("LoopController"),
titleColor: "#02A7F0",
titleBgColor: "#F4F4F5",
icon: "next_plan",
click: () => {
this.addComponent('LoopController')
}
},
{ {
title: this.$t('api_test.automation.wait_controller'), title: this.$t('api_test.automation.wait_controller'),
show: this.showButton("ConstantTimer"), show: this.showButton("ConstantTimer"),
@ -494,8 +508,11 @@
this.customizeRequest = {protocol: "HTTP", type: "API", hashTree: [], referenced: 'Created', active: false}; this.customizeRequest = {protocol: "HTTP", type: "API", hashTree: [], referenced: 'Created', active: false};
this.customizeVisible = true; this.customizeVisible = true;
break; break;
case ELEMENT_TYPE.LoopController:
this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new LoopController()) :
this.scenarioDefinition.push(new LoopController());
break;
case ELEMENT_TYPE.scenario: case ELEMENT_TYPE.scenario:
// this.scenarioVisible = true;
this.$refs.scenarioRelevance.open(); this.$refs.scenarioRelevance.open();
break; break;
default: default:
@ -613,8 +630,7 @@
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => { this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.maintainerOptions = response.data; this.maintainerOptions = response.data;
}); });
} },
,
openTagConfig() { openTagConfig() {
if (!this.projectId) { if (!this.projectId) {
this.$error(this.$t('api_test.select_project')); this.$error(this.$t('api_test.select_project'));
@ -623,7 +639,8 @@
this.$refs.tag.open(); this.$refs.tag.open();
}, },
remove(row, node) { remove(row, node) {
this.$alert(this.$t('api_test.definition.request.delete_confirm') + ' ' + row.name + " ", '', { let name = row.name === undefined ? "" : row.name;
this.$alert(this.$t('api_test.definition.request.delete_confirm_step') + ' ' + name + " ", '', {
confirmButtonText: this.$t('commons.confirm'), confirmButtonText: this.$t('commons.confirm'),
callback: (action) => { callback: (action) => {
if (action === 'confirm') { if (action === 'confirm') {
@ -636,8 +653,7 @@
} }
} }
}); });
} },
,
copyRow(row, node) { copyRow(row, node) {
const parent = node.parent const parent = node.parent
const hashTree = parent.data.hashTree || parent.data; const hashTree = parent.data.hashTree || parent.data;
@ -654,8 +670,7 @@
this.$nextTick(() => { this.$nextTick(() => {
this.loading = false this.loading = false
}) })
} },
,
runDebug() { runDebug() {
/*触发执行操作*/ /*触发执行操作*/
if (!this.currentEnvironmentId) { if (!this.currentEnvironmentId) {
@ -668,8 +683,7 @@
environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition
}; };
this.reportId = getUUID().substring(0, 8); this.reportId = getUUID().substring(0, 8);
} },
,
getEnvironments() { getEnvironments() {
if (this.projectId) { if (this.projectId) {
this.$get('/api/environment/list/' + this.projectId, response => { this.$get('/api/environment/list/' + this.projectId, response => {
@ -689,16 +703,14 @@
} }
}); });
} }
} },
,
openEnvironmentConfig() { openEnvironmentConfig() {
if (!this.projectId) { if (!this.projectId) {
this.$error(this.$t('api_test.select_project')); this.$error(this.$t('api_test.select_project'));
return; return;
} }
this.$refs.environmentConfig.open(this.projectId); this.$refs.environmentConfig.open(this.projectId);
} },
,
environmentConfigClose() { environmentConfigClose() {
this.getEnvironments(); this.getEnvironments();
} }
@ -712,25 +724,21 @@
return true; return true;
} }
return false; return false;
} },
,
allowDrag(draggingNode, dropNode, dropType) { allowDrag(draggingNode, dropNode, dropType) {
this.sort(); this.sort();
this.reload(); this.reload();
} },
,
nodeExpand(data) { nodeExpand(data) {
if (data.resourceId) { if (data.resourceId) {
this.expandedNode.push(data.resourceId); this.expandedNode.push(data.resourceId);
} }
} },
,
nodeCollapse(data) { nodeCollapse(data) {
if (data.resourceId) { if (data.resourceId) {
this.expandedNode.splice(this.expandedNode.indexOf(data.resourceId), 1); this.expandedNode.splice(this.expandedNode.indexOf(data.resourceId), 1);
} }
} },
,
getPath(id) { getPath(id) {
if (id === null) { if (id === null) {
return null; return null;
@ -739,8 +747,7 @@
return item.id === id ? item.path : ""; return item.id === id ? item.path : "";
}); });
return path[0].path; return path[0].path;
} },
,
setFiles(item, bodyUploadFiles, obj) { setFiles(item, bodyUploadFiles, obj) {
if (item.body) { if (item.body) {
if (item.body.kvs) { if (item.body.kvs) {
@ -778,8 +785,7 @@
}); });
} }
} }
} },
,
recursiveFile(arr, bodyUploadFiles, obj) { recursiveFile(arr, bodyUploadFiles, obj) {
arr.forEach(item => { arr.forEach(item => {
this.setFiles(item, bodyUploadFiles, obj); this.setFiles(item, bodyUploadFiles, obj);
@ -787,8 +793,7 @@
this.recursiveFile(item.hashTree, bodyUploadFiles, obj); this.recursiveFile(item.hashTree, bodyUploadFiles, obj);
} }
}); });
} },
,
getBodyUploadFiles(obj) { getBodyUploadFiles(obj) {
let bodyUploadFiles = []; let bodyUploadFiles = [];
obj.bodyUploadIds = []; obj.bodyUploadIds = [];
@ -799,8 +804,7 @@
} }
}) })
return bodyUploadFiles; return bodyUploadFiles;
} },
,
editScenario() { editScenario() {
this.$refs['currentScenario'].validate((valid) => { this.$refs['currentScenario'].validate((valid) => {
if (valid) { if (valid) {

View File

@ -0,0 +1,189 @@
<template>
<el-card v-loading="loading">
<el-row>
<div class="el-step__icon is-text ms-api-col">
<div class="el-step__icon-inner">{{controller.index}}</div>
</div>
<el-button class="ms-title-buttion" size="small">{{$t('api_test.automation.loop_controller')}}</el-button>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="LOOP_COUNT">{{$t('loop.loops_title')}}</el-radio>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="FOREACH">{{$t('loop.foreach')}}</el-radio>
<el-radio @change="changeRadio" class="ms-radio" v-model="controller.loopType" label="WHILE">{{$t('loop.while')}}</el-radio>
<div style="margin-right: 20px; float: right">
<i class="icon el-icon-arrow-right" :class="{'is-active': controller.active}"
@click="active(controller)"/>
<el-switch v-model="controller.enable" style="margin-left: 10px"/>
<el-button size="mini" icon="el-icon-copy-document" circle @click="copyRow" style="margin-left: 10px"/>
<el-button size="mini" icon="el-icon-delete" type="danger" circle @click="remove" style="margin-left: 10px"/>
</div>
</el-row>
<el-collapse-transition>
<div v-if="controller.active" style="margin-top: 20px;">
<div v-if="controller.loopType==='LOOP_COUNT'">
<el-row>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.loops')}}</span>
<el-input-number size="small" v-model="controller.countController.loops" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.countController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.proceed')}}</span>
<el-switch v-model="controller.countController.proceed"/>
</el-col>
</el-row>
</div>
<div v-else-if="controller.loopType==='FOREACH'">
<el-row>
<el-col :span="8">
<el-input :placeholder="$t('api_test.request.condition_variable')" v-model="controller.forEachController.inputVal" size="small"/>
</el-col>
<el-col :span="1" style="margin-top: 6px">
<span style="margin:10px 10px 10px">in</span>
</el-col>
<el-col :span="8">
<el-input :placeholder="$t('api_test.request.condition_variable')" v-model="controller.forEachController.returnVal" size="small"/>
</el-col>
<el-col :span="7">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.forEachController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</el-col>
</el-row>
</div>
<div v-else>
<el-input size="small" v-model="controller.whileController.variable" style="width: 20%" :placeholder="$t('api_test.request.condition_variable')"/>
<el-select v-model="controller.whileController.operator" :placeholder="$t('commons.please_select')" size="small"
@change="change" style="width: 10%;margin-left: 10px">
<el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/>
</el-select>
<el-input size="small" v-model="controller.whileController.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/>
<span class="ms-span ms-radio">{{$t('loop.timeout')}}</span>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
</div>
</div>
</el-collapse-transition>
</el-card>
</template>
<script>
export default {
name: "MsLoopController",
props: {
controller: {},
node: {},
index: Object,
},
data() {
return {
loading: false,
operators: {
EQ: {
label: "commons.adv_search.operators.equals",
value: "=="
},
NE: {
label: "commons.adv_search.operators.not_equals",
value: "!="
},
LIKE: {
label: "commons.adv_search.operators.like",
value: "=~"
},
NOT_LIKE: {
label: "commons.adv_search.operators.not_like",
value: "!~"
},
GT: {
label: "commons.adv_search.operators.gt",
value: ">"
},
LT: {
label: "commons.adv_search.operators.lt",
value: "<"
},
IS_EMPTY: {
label: "commons.adv_search.operators.is_empty",
value: "is empty"
},
IS_NOT_EMPTY: {
label: "commons.adv_search.operators.is_not_empty",
value: "is not empty"
}
}
}
},
methods: {
remove() {
this.$emit('remove', this.controller, this.node);
},
copyRow() {
this.$emit('copyRow', this.controller, this.node);
},
active(item) {
item.active = !item.active;
this.reload();
},
changeRadio() {
this.controller.active = true;
this.reload();
},
change(value) {
if (value.indexOf("empty") > 0 && !!this.controller.value) {
this.controller.value = "";
}
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
},
computed: {
hasEmptyOperator() {
return !!this.controller.operator && this.controller.operator.indexOf("empty") > 0;
}
}
}
</script>
<style scoped>
.ms-api-col {
background-color: #F4F4F5;
border-color: #02A7F0;
margin-right: 10px;
color: #02A7F0;
}
.ms-title-buttion {
background-color: #F4F4F5;
margin-right: 20px;
color: #02A7F0;
}
.icon.is-active {
transform: rotate(90deg);
}
.ms-span {
margin: 10px;
}
.ms-radio {
color: #606266;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
font-size: 13px;
font-weight: normal;
}
</style>

View File

@ -1,5 +1,5 @@
export const ELEMENTS = new Map([ export const ELEMENTS = new Map([
['ALL', ["scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "IfController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]], ['ALL', ["scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "IfController", "LoopController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]],
['scenario', ["HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "CASE", "OT_IMPORT", "IfController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]], ['scenario', ["HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "CASE", "OT_IMPORT", "IfController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]],
['HTTPSamplerProxy', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['HTTPSamplerProxy', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['DubboSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['DubboSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
@ -7,6 +7,7 @@ export const ELEMENTS = new Map([
['TCPSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['TCPSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['OT_IMPORT', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['OT_IMPORT', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['IfController', ["IfController", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]], ['IfController', ["IfController", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]],
['LoopController', ["IfController", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]],
['ConstantTimer', []], ['ConstantTimer', []],
['JSR223Processor', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['JSR223Processor', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['JSR223PreProcessor', []], ['JSR223PreProcessor', []],
@ -27,6 +28,7 @@ export const ELEMENT_TYPE = {
JSR223PostProcessor: "JSR223PostProcessor", JSR223PostProcessor: "JSR223PostProcessor",
Assertions: "Assertions", Assertions: "Assertions",
Extract: "Extract", Extract: "Extract",
CustomizeReq: "CustomizeReq" CustomizeReq: "CustomizeReq",
LoopController: "LoopController"
} }

View File

@ -1016,6 +1016,38 @@ export class IfController extends Controller {
} }
} }
export class LoopController extends Controller {
constructor(options = {}) {
super("LoopController", options);
this.type = "LoopController";
this.active = false;
this.loopType = "LOOP_COUNT";
this.countController = {loops: 0, interval: 0, proceed: false};
this.forEachController = {inputVal: "", returnVal: "", interval: 0};
this.whileController = {variable: "", operator: "", value: "", timeout: 0};
this.hashTree = [];
this.set(options);
}
isValid() {
if (!!this.operator && this.operator.indexOf("empty") > 0) {
return !!this.variable && !!this.operator;
}
return !!this.variable && !!this.operator && !!this.value;
}
label() {
if (this.isValid()) {
let label = this.variable;
if (this.operator) label += " " + this.operator;
if (this.value) label += " " + this.value;
return label;
}
return "";
}
}
export class Timer extends BaseConfig { export class Timer extends BaseConfig {
static TYPES = { static TYPES = {
CONSTANT_TIMER: "Constant Timer", CONSTANT_TIMER: "Constant Timer",

View File

@ -544,6 +544,7 @@ export default {
res_param: "Response content", res_param: "Response content",
batch_delete: "Batch deletion", batch_delete: "Batch deletion",
delete_confirm: "Confirm deletion", delete_confirm: "Confirm deletion",
delete_confirm_step: "Confirm deletion step",
assertions_rule: "Assertion rule", assertions_rule: "Assertion rule",
response_header: "Response header", response_header: "Response header",
response_body: "Response body", response_body: "Response body",
@ -574,6 +575,7 @@ export default {
external_import: "External import", external_import: "External import",
wait_controller: "Wait controller", wait_controller: "Wait controller",
if_controller: "If controller", if_controller: "If controller",
loop_controller: "Loop Controller",
scenario_import: "Scenario import", scenario_import: "Scenario import",
customize_script: "Customize script", customize_script: "Customize script",
customize_req: "Customize req", customize_req: "Customize req",
@ -1259,8 +1261,8 @@ export default {
host: 'Host number cannot be empty', host: 'Host number cannot be empty',
port: 'Port cannot be empty', port: 'Port cannot be empty',
account: 'Account cannot be empty', account: 'Account cannot be empty',
test_recipients:'Test recipients', test_recipients: 'Test recipients',
tip:'Tip: use as test mail recipient only', tip: 'Tip: use as test mail recipient only',
}, },
i18n: { i18n: {
@ -1414,5 +1416,15 @@ export default {
nothing: "Nothing", nothing: "Nothing",
preview: "Preview", preview: "Preview",
add_custom: "Add Custom Prop" add_custom: "Add Custom Prop"
},
loop: {
loops_title: "loops",
foreach: "ForEach",
while: "While",
loops: "loops",
interval: "interval",
proceed: "proceed",
timeout: "timeout",
} }
}; };

View File

@ -543,6 +543,7 @@ export default {
res_param: "响应内容", res_param: "响应内容",
batch_delete: "批量删除", batch_delete: "批量删除",
delete_confirm: "确认删除接口", delete_confirm: "确认删除接口",
delete_confirm_step: "确认删除步骤",
assertions_rule: "断言规则", assertions_rule: "断言规则",
response_header: "响应头", response_header: "响应头",
response_body: "响应体", response_body: "响应体",
@ -573,6 +574,7 @@ export default {
external_import: "外部导入", external_import: "外部导入",
wait_controller: "等待控制器", wait_controller: "等待控制器",
if_controller: "条件控制器", if_controller: "条件控制器",
loop_controller: "循环控制器",
scenario_import: "场景导入", scenario_import: "场景导入",
customize_script: "自定义脚本", customize_script: "自定义脚本",
customize_req: "自定义请求", customize_req: "自定义请求",
@ -1260,8 +1262,8 @@ export default {
host: '主机号不能为空', host: '主机号不能为空',
port: '端口号不能为空', port: '端口号不能为空',
account: '账户不能为空', account: '账户不能为空',
test_recipients:'测试收件人', test_recipients: '测试收件人',
tip:'提示:仅用来作为测试邮件收件人', tip: '提示:仅用来作为测试邮件收件人',
}, },
i18n: { i18n: {
home: '首页', home: '首页',
@ -1414,5 +1416,14 @@ export default {
nothing: "无", nothing: "无",
preview: "预览", preview: "预览",
add_custom: "添加自定义属性" add_custom: "添加自定义属性"
},
loop: {
loops_title: "次数循环",
foreach: "ForEach 循环",
while: "While 循环",
loops: "循环次数",
interval: "循环间隔",
proceed: "成功后继续循环",
timeout: "循环超时时间",
} }
}; };

View File

@ -543,6 +543,7 @@ export default {
res_param: "響應内容", res_param: "響應内容",
batch_delete: "批量删除", batch_delete: "批量删除",
delete_confirm: "確認刪除接口", delete_confirm: "確認刪除接口",
delete_confirm_step: "確認刪除步骤",
assertions_rule: "斷言規則", assertions_rule: "斷言規則",
response_header: "響應頭", response_header: "響應頭",
response_body: "響應體", response_body: "響應體",
@ -573,6 +574,7 @@ export default {
external_import: "外部導入", external_import: "外部導入",
wait_controller: "等待控制器", wait_controller: "等待控制器",
if_controller: "條件控制器", if_controller: "條件控制器",
loop_controller: "循环控制器",
scenario_import: "場景導入", scenario_import: "場景導入",
customize_script: "自定義脚本", customize_script: "自定義脚本",
customize_req: "自定義請求", customize_req: "自定義請求",
@ -1259,8 +1261,8 @@ export default {
host: '主機號不能為空', host: '主機號不能為空',
port: '端口號不能為空', port: '端口號不能為空',
account: '賬戶不能為空', account: '賬戶不能為空',
test_recipients:'測試收件人', test_recipients: '測試收件人',
tip:'提示:僅用來作為測試郵件收件人', tip: '提示:僅用來作為測試郵件收件人',
}, },
i18n: { i18n: {
home: '首頁', home: '首頁',
@ -1413,5 +1415,15 @@ export default {
nothing: "无", nothing: "无",
preview: "预览", preview: "预览",
add_custom: "添加自定义属性" add_custom: "添加自定义属性"
},
loop: {
loops_title: "次數循環",
foreach: "ForEach 循環",
while: "While 循環",
loops: "循環次数",
interval: "循環間隔",
proceed: "成功後繼續循環",
timeout: "循環超時時間",
} }
}; };