diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java index d1cf69ae9b..823f29f47f 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java @@ -10,6 +10,7 @@ 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.controller.MsIfController; import io.metersphere.api.dto.definition.request.controller.MsLoopController; +import io.metersphere.api.dto.definition.request.controller.MsTransactionController; 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.post.MsJSR223PostProcessor; @@ -71,6 +72,7 @@ import java.util.stream.Collectors; @JsonSubTypes.Type(value = MsJDBCSampler.class, name = "JDBCSampler"), @JsonSubTypes.Type(value = MsConstantTimer.class, name = "ConstantTimer"), @JsonSubTypes.Type(value = MsIfController.class, name = "IfController"), + @JsonSubTypes.Type(value = MsTransactionController.class, name = "TransactionController"), @JsonSubTypes.Type(value = MsScenario.class, name = "scenario"), @JsonSubTypes.Type(value = MsLoopController.class, name = "LoopController"), @JsonSubTypes.Type(value = MsJmeterElement.class, name = "JmeterElement"), @@ -78,7 +80,7 @@ import java.util.stream.Collectors; }) @JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223Processor.class, MsJSR223PostProcessor.class, MsJSR223PreProcessor.class, MsTestPlan.class, MsThreadGroup.class, MsAuthManager.class, MsAssertions.class, - MsExtract.class, MsTCPSampler.class, MsDubboSampler.class, MsJDBCSampler.class, MsConstantTimer.class, MsIfController.class, MsScenario.class, MsLoopController.class, MsJmeterElement.class}, typeKey = "type") + MsExtract.class, MsTCPSampler.class, MsDubboSampler.class, MsJDBCSampler.class, MsConstantTimer.class, MsIfController.class,MsTransactionController.class, MsScenario.class, MsLoopController.class, MsJmeterElement.class}, typeKey = "type") @Data public abstract class MsTestElement { private String type; @@ -285,6 +287,15 @@ public abstract class MsTestElement { if (StringUtils.equals(loopController.getLoopType(), LoopConstants.LOOP_COUNT.name()) && loopController.getCountController() != null) { return "次数循环-" + "${MS_LOOP_CONTROLLER_CONFIG}"; } + }else if(MsTestElementConstants.TransactionController.name().equals(parent.getType())){ + MsTransactionController transactionController = (MsTransactionController)parent; + if(StringUtils.isNotEmpty(transactionController.getName())){ + return transactionController.getName(); + }else if(StringUtils.isNotEmpty(transactionController.getLabelName())){ + return transactionController.getLabelName(); + }else { + return "TransactionController"; + } } // 获取全路径以备后面使用 String fullPath = getFullPath(parent, new String()); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsTransactionController.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsTransactionController.java new file mode 100644 index 0000000000..ce392099a5 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsTransactionController.java @@ -0,0 +1,71 @@ +package io.metersphere.api.dto.definition.request.controller; + +import com.alibaba.fastjson.annotation.JSONType; +import io.metersphere.api.dto.definition.request.MsTestElement; +import io.metersphere.api.dto.definition.request.ParameterConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.control.IfController; +import org.apache.jmeter.control.TransactionController; +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 = "TransactionController") +public class MsTransactionController extends MsTestElement { + private String type = "TransactionController"; + private String name; + private boolean generateParentSample; + private boolean includeTimers; + + @Override + public void toHashTree(HashTree tree, List hashTree, ParameterConfig config) { + // 非导出操作,且不是启用状态则跳过执行 + if (!config.isOperating() && !this.isEnable()) { + return; + } + final HashTree groupTree = tree.add(transactionController()); + if (CollectionUtils.isNotEmpty(hashTree)) { + hashTree.forEach(el -> { + // 给所有孩子加一个父亲标志 + el.setParent(this); + el.toHashTree(groupTree, el.getHashTree(), config); + }); + } + } + + private TransactionController transactionController() { + TransactionController transactionController = new TransactionController(); + transactionController.setEnabled(this.isEnable()); + if (StringUtils.isEmpty(this.getName())) { + this.setName(getLabelName()); + } + transactionController.setName(this.getName()); + transactionController.setProperty(TestElement.TEST_CLASS, IfController.class.getName()); + transactionController.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TransactionControllerGui")); + transactionController.setGenerateParentSample(generateParentSample); + transactionController.setIncludeTimers(includeTimers); + return transactionController; + } + + public boolean isValid() { + return StringUtils.isNotBlank(name); + } + + public String getLabelName() { + if (isValid()) { + String label = "事务控制器:"; + if (StringUtils.isNotBlank(name)) { + label += " " + this.name; + } + return label; + } + return "TransactionController"; + } +} diff --git a/backend/src/main/java/io/metersphere/commons/constants/MsTestElementConstants.java b/backend/src/main/java/io/metersphere/commons/constants/MsTestElementConstants.java index 81f0415d05..3a1ddb3fc7 100644 --- a/backend/src/main/java/io/metersphere/commons/constants/MsTestElementConstants.java +++ b/backend/src/main/java/io/metersphere/commons/constants/MsTestElementConstants.java @@ -1,5 +1,5 @@ package io.metersphere.commons.constants; public enum MsTestElementConstants { - LoopController,SCENARIO,REF,Deleted + LoopController,SCENARIO,REF,Deleted,TransactionController } diff --git a/frontend/src/business/components/api/automation/report/components/RequestResult.vue b/frontend/src/business/components/api/automation/report/components/RequestResult.vue index 7c91693939..4bee4f00a4 100644 --- a/frontend/src/business/components/api/automation/report/components/RequestResult.vue +++ b/frontend/src/business/components/api/automation/report/components/RequestResult.vue @@ -13,7 +13,7 @@ - +
{{ request.responseResult.responseCode }}
diff --git a/frontend/src/business/components/api/automation/report/components/RequestSubResult.vue b/frontend/src/business/components/api/automation/report/components/RequestSubResult.vue index fa258162ac..f6a0f090df 100644 --- a/frontend/src/business/components/api/automation/report/components/RequestSubResult.vue +++ b/frontend/src/business/components/api/automation/report/components/RequestSubResult.vue @@ -5,13 +5,16 @@
-
-
{{ indexNumber+1 }}
+ +
+
+
{{ indexNumber+1 }}
+
+
+
{{ indexNumber+1 }}
+
+ {{ request.name }}
-
-
{{ indexNumber+1 }}
-
- {{ request.name }}
@@ -21,7 +24,11 @@
- {{ request.url }} + +
+ {{ request.url }} +
+
diff --git a/frontend/src/business/components/api/automation/report/components/ScenarioResult.vue b/frontend/src/business/components/api/automation/report/components/ScenarioResult.vue index 7e5b601a27..ed17fa3b57 100644 --- a/frontend/src/business/components/api/automation/report/components/ScenarioResult.vue +++ b/frontend/src/business/components/api/automation/report/components/ScenarioResult.vue @@ -6,7 +6,6 @@
{{ node.index }}
{{node.label}} -
diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index 64a34519a6..59d5e0049c 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -238,7 +238,8 @@ Extract, IfController, JSR223Processor, - LoopController + LoopController, + TransactionController } from "../../definition/model/ApiTestModel"; import {parseEnvironment} from "../../definition/model/EnvironmentModel"; import {ELEMENT_TYPE, ELEMENTS} from "./Setting"; @@ -426,6 +427,16 @@ this.addComponent('LoopController') } }, + { + title: this.$t('api_test.automation.transcation_controller'), + show: this.showButton("TransactionController"), + titleColor: "#6D317C", + titleBgColor: "#F4F4F5", + icon: "alt_route", + click: () => { + this.addComponent('TransactionController') + } + }, { title: this.$t('api_test.automation.wait_controller'), show: this.showButton("ConstantTimer"), @@ -544,31 +555,31 @@ addComponent(type) { switch (type) { case ELEMENT_TYPE.IfController: - this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new IfController()) : + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new IfController()) : this.scenarioDefinition.push(new IfController()); break; case ELEMENT_TYPE.ConstantTimer: - this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new ConstantTimer()) : + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new ConstantTimer()) : this.scenarioDefinition.push(new ConstantTimer()); break; case ELEMENT_TYPE.JSR223Processor: - this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new JSR223Processor()) : + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new JSR223Processor()) : this.scenarioDefinition.push(new JSR223Processor()); break; case ELEMENT_TYPE.JSR223PreProcessor: - this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new JSR223Processor({type: "JSR223PreProcessor"})) : + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new JSR223Processor({type: "JSR223PreProcessor"})) : this.scenarioDefinition.push(new JSR223Processor({type: "JSR223PreProcessor"})); break; case ELEMENT_TYPE.JSR223PostProcessor: - this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new JSR223Processor({type: "JSR223PostProcessor"})) : + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new JSR223Processor({type: "JSR223PostProcessor"})) : this.scenarioDefinition.push(new JSR223Processor({type: "JSR223PostProcessor"})); break; case ELEMENT_TYPE.Assertions: - this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new Assertions()) : + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new Assertions()) : this.scenarioDefinition.push(new Assertions()); break; case ELEMENT_TYPE.Extract: - this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new Extract()) : + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new Extract()) : this.scenarioDefinition.push(new Extract()); break; case ELEMENT_TYPE.CustomizeReq: @@ -576,9 +587,13 @@ this.customizeVisible = true; break; case ELEMENT_TYPE.LoopController: - this.selectedTreeNode != undefined ? this.selectedTreeNode.hashTree.push(new LoopController()) : + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new LoopController()) : this.scenarioDefinition.push(new LoopController()); break; + case ELEMENT_TYPE.TransactionController: + this.selectedTreeNode !== undefined ? this.selectedTreeNode.hashTree.push(new TransactionController()) : + this.scenarioDefinition.push(new TransactionController()); + break; case ELEMENT_TYPE.scenario: this.isBtnHide = true; this.$refs.scenarioRelevance.open(); diff --git a/frontend/src/business/components/api/automation/scenario/Setting.js b/frontend/src/business/components/api/automation/scenario/Setting.js index ff3a6fe9cb..8587a41fd2 100644 --- a/frontend/src/business/components/api/automation/scenario/Setting.js +++ b/frontend/src/business/components/api/automation/scenario/Setting.js @@ -1,5 +1,5 @@ export const ELEMENTS = new Map([ - ['ALL', ["scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "IfController", "LoopController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]], + ['ALL', ["scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "IfController","TransactionController", "LoopController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]], ['scenario', ["HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "CASE", "OT_IMPORT", "IfController", "ConstantTimer", "JSR223Processor", "CustomizeReq"]], ['HTTPSamplerProxy', ["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"]], ['OT_IMPORT', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['IfController', ["IfController", "scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]], + ['TransactionController', ["TransactionController", "scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]], ['LoopController', ["IfController", "scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]], ['ConstantTimer', []], ['JSR223Processor', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], @@ -26,6 +27,7 @@ export const ELEMENT_TYPE = { HTTPSamplerProxy: "HTTPSamplerProxy", OT_IMPORT: "OT_IMPORT", IfController: "IfController", + TransactionController: "TransactionController", ConstantTimer: "ConstantTimer", JSR223Processor: "JSR223Processor", JSR223PreProcessor: "JSR223PreProcessor", diff --git a/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue b/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue index 5334ef8865..29ffdb61f4 100644 --- a/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue +++ b/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue @@ -12,6 +12,7 @@ + + diff --git a/frontend/src/business/components/api/definition/model/ApiTestModel.js b/frontend/src/business/components/api/definition/model/ApiTestModel.js index d5f163df7e..611df32709 100644 --- a/frontend/src/business/components/api/definition/model/ApiTestModel.js +++ b/frontend/src/business/components/api/definition/model/ApiTestModel.js @@ -12,6 +12,7 @@ import { HTTPsamplerFiles, HTTPSamplerProxy, IfController as JMXIfController, + TransactionController as JMXTransactionController, JDBCDataSource, JDBCSampler, JSONPathAssertion, @@ -1051,6 +1052,34 @@ export class LoopController extends Controller { } } +export class TransactionController extends Controller { + constructor(options = {}) { + super("TransactionController", options); + this.type = "TransactionController"; + this.name; + 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.$t('api_test.automation.transcation_controller'); + if(this.name != null && this.name !== ""){ + label = this.name; + } + return label; + } + return ""; + } +} + export class Timer extends BaseConfig { static TYPES = { diff --git a/frontend/src/business/components/api/definition/model/JMX.js b/frontend/src/business/components/api/definition/model/JMX.js index 3a10643de3..38d26a34ca 100644 --- a/frontend/src/business/components/api/definition/model/JMX.js +++ b/frontend/src/business/components/api/definition/model/JMX.js @@ -536,6 +536,16 @@ export class IfController extends DefaultTestElement { } } +export class TransactionController extends DefaultTestElement { + constructor(testName, controller = {}) { + super('TransactionController', 'TransactionControllerPanel', 'TransactionController', testName); + + this.stringProp('TransactionController.comments', controller.comments); + this.boolProp('TransactionController.parent', controller.parent, true); + } +} + + export class ConstantTimer extends DefaultTestElement { constructor(testName, timer = {}) { super('ConstantTimer', 'ConstantTimerGui', 'ConstantTimer', testName); diff --git a/frontend/src/business/components/api/test/model/JMX.js b/frontend/src/business/components/api/test/model/JMX.js index 3368c376de..2d4307b382 100644 --- a/frontend/src/business/components/api/test/model/JMX.js +++ b/frontend/src/business/components/api/test/model/JMX.js @@ -552,6 +552,15 @@ export class IfController extends DefaultTestElement { } } +export class TransactionController extends DefaultTestElement { + constructor(testName, controller = {}) { + super('TransactionController', 'TransactionControllerPanel', 'TransactionController', testName); + + this.stringProp('TransactionController.comments', controller.comments); + this.boolProp('TransactionController.parent', controller.parent, true); + } +} + export class ConstantTimer extends DefaultTestElement { constructor(testName, timer = {}) { super('ConstantTimer', 'ConstantTimerGui', 'ConstantTimer', testName); diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index 092c2addc1..04a5916a62 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -12,6 +12,7 @@ import { HTTPsamplerFiles, HTTPSamplerProxy, IfController as JMXIfController, + TransactionController as JMXTransactionController, JDBCDataSource, JDBCSampler, JSONPathAssertion, @@ -986,6 +987,35 @@ export class IfController extends Controller { } } +export class TransactionController extends Controller { + constructor(options = {}) { + super("TransactionController", options); + this.type = "TransactionController"; + this.variable; + this.operator; + this.value; + 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 { static TYPES = { CONSTANT_TIMER: "Constant Timer", diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 30a0c27655..d07d8391ad 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -809,6 +809,7 @@ export default { wait_controller: "Wait controller", if_controller: "If controller", loop_controller: "Loop Controller", + transcation_controller:"Transcation controller", scenario_import: "Scenario import", customize_script: "Customize script", customize_req: "Customize req", diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 3a18b1b6d4..105e1313c3 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -810,6 +810,7 @@ export default { wait_controller: "等待控制器", if_controller: "条件控制器", loop_controller: "循环控制器", + transcation_controller:"事务控制器", scenario_import: "场景导入", customize_script: "自定义脚本", customize_req: "自定义请求", diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 19617cdfde..0c013de26b 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -810,6 +810,7 @@ export default { wait_controller: "等待控制器", if_controller: "條件控制器", loop_controller: "循環控制器", + transcation_controller:"事務控制器", scenario_import: "場景導入", customize_script: "自定義腳本", customize_req: "自定義請求",