feat: 接口场景增加事务控制器并优化报告显示

接口场景增加事务控制器并优化报告显示
This commit is contained in:
song-tianyang 2021-05-17 17:01:54 +08:00 committed by 刘瑞斌
parent c33e6bf947
commit 7e0e476337
17 changed files with 336 additions and 22 deletions

View File

@ -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());

View File

@ -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<MsTestElement> 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";
}
}

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum MsTestElementConstants {
LoopController,SCENARIO,REF,Deleted
LoopController,SCENARIO,REF,Deleted,TransactionController
}

View File

@ -13,7 +13,7 @@
</div>
</el-col>
<el-col :span="9">
<el-tooltip effect="dark" :content="request.responseResult.responseCode" placement="bottom" :open-delay="800">
<el-tooltip effect="dark" :content="request.responseResult.responseCode" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom" :open-delay="800">
<div style="color: #5daf34" v-if="request.success">
{{ request.responseResult.responseCode }}
</div>

View File

@ -5,13 +5,16 @@
<el-row :gutter="10" type="flex" align="middle" class="info">
<el-col :span="6" v-if="indexNumber!=undefined">
<div class="method">
<div class="el-step__icon is-text ms-api-col" v-if="indexNumber%2 ==0">
<div class="el-step__icon-inner"> {{ indexNumber+1 }}</div>
<div style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
<div class="el-step__icon is-text ms-api-col" v-if="indexNumber%2 ==0">
<div class="el-step__icon-inner"> {{ indexNumber+1 }}</div>
</div>
<div class="el-step__icon is-text ms-api-col-create" v-else>
<div class="el-step__icon-inner"> {{ indexNumber+1 }}</div>
</div>
{{ request.name }}
</div>
<div class="el-step__icon is-text ms-api-col-create" v-else>
<div class="el-step__icon-inner"> {{ indexNumber+1 }}</div>
</div>
{{ request.name }}
</div>
</el-col>
<el-col :span="2">
@ -21,7 +24,11 @@
</el-col>
<el-col :span="6">
<div class="url">
{{ request.url }}
<el-tooltip :content="request.url " style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom" :open-delay="800">
<div>
{{ request.url }}
</div>
</el-tooltip>
</div>
</el-col>
<el-col :span="5">

View File

@ -6,7 +6,6 @@
<div class="el-step__icon-inner"> {{ node.index }}</div>
</div>
{{node.label}}
</el-card>
</div>
<div v-else>

View File

@ -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();

View File

@ -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",

View File

@ -12,6 +12,7 @@
<script>
import MsConstantTimer from "./ConstantTimer";
import MsIfController from "./IfController";
import MsTransactionController from "./TransactionController";
import {ELEMENT_TYPE} from "../Setting";
import MsJsr233Processor from "./Jsr233Processor";
import MsApiAssertions from "../../../definition/components/assertion/ApiAssertions";
@ -23,7 +24,7 @@
export default {
name: "ComponentConfig",
components: {MsConstantTimer, MsIfController, MsJsr233Processor, MsApiAssertions, MsApiExtract, MsApiComponent, MsLoopController, MsApiScenarioComponent, JmeterElementComponent},
components: {MsConstantTimer, MsIfController, MsTransactionController,MsJsr233Processor, MsApiAssertions, MsApiExtract, MsApiComponent, MsLoopController, MsApiScenarioComponent, JmeterElementComponent},
props: {
type: String,
scenario: {},
@ -60,6 +61,9 @@
case ELEMENT_TYPE.IfController:
name = "MsIfController";
break;
case ELEMENT_TYPE.TransactionController:
name = "MsTransactionController";
break;
case ELEMENT_TYPE.ConstantTimer:
name = "MsConstantTimer";
break;

View File

@ -0,0 +1,124 @@
<template>
<api-base-component
@copy="copyRow"
@remove="remove"
:data="controller"
:show-collapse="false"
:draggable="draggable"
:is-max="isMax"
:show-btn="showBtn"
color="#6D317C"
background-color="#FCF6EE"
:title="$t('api_test.automation.transcation_controller')">
<template v-slot:headerLeft>
<el-input draggable size="mini" v-model="controller.name" style="width: 20%" :placeholder="$t('api_test.automation.transcation_controller')"/>
<el-checkbox v-model="controller.generateParentSample" style="margin-left: 20px">Generate Parent Sample</el-checkbox>
<el-checkbox v-model="controller.includeTimers">Include Timers</el-checkbox>
</template>
</api-base-component>
</template>
<script>
import ApiBaseComponent from "../common/ApiBaseComponent";
export default {
name: "MsTransactionController",
components: {ApiBaseComponent},
props: {
controller: {},
node: {},
isMax: {
type: Boolean,
default: false,
},
showBtn: {
type: Boolean,
default: true,
},
index: Object,
draggable: {
type: Boolean,
default: false,
},
},
created() {
if(this.controller.generateParentSample == null){
this.controller.generateParentSample = true;
}
if(this.controller.includeTimers == null){
this.controller.includeTimers = true;
}
},
data() {
return {
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);
},
change(value) {
if (value.indexOf("empty") > 0 && !!this.controller.value) {
this.controller.value = "";
}
}
},
computed: {
hasEmptyOperator() {
return !!this.controller.operator && this.controller.operator.indexOf("empty") > 0;
}
}
}
</script>
<style scoped>
.ms-btn {
width: 20%;
margin-left: 5px;
}
.ms-select {
width: 15%;
margin-left: 5px;
}
</style>

View File

@ -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 = {

View File

@ -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);

View File

@ -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);

View File

@ -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",

View File

@ -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",

View File

@ -810,6 +810,7 @@ export default {
wait_controller: "等待控制器",
if_controller: "条件控制器",
loop_controller: "循环控制器",
transcation_controller:"事务控制器",
scenario_import: "场景导入",
customize_script: "自定义脚本",
customize_req: "自定义请求",

View File

@ -810,6 +810,7 @@ export default {
wait_controller: "等待控制器",
if_controller: "條件控制器",
loop_controller: "循環控制器",
transcation_controller:"事務控制器",
scenario_import: "場景導入",
customize_script: "自定義腳本",
customize_req: "自定義請求",