fix(接口自动化): 允许引用用例新增步骤

--bug=1007648 --user=赵勇 引用的接口用例无法添加等待时间 https://www.tapd.cn/55049933/s/1062875
This commit is contained in:
fit2-zhao 2021-11-03 15:38:26 +08:00 committed by 刘瑞斌
parent 128a20245f
commit 70536148e0
11 changed files with 153 additions and 37 deletions

View File

@ -3,6 +3,9 @@ package io.metersphere.api.dto.definition.request;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
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.variable.ScenarioVariable;
@ -330,6 +333,8 @@ public class ElementUtil {
public static void dataSetDomain(JSONArray hashTree, MsParameter msParameter) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
for (int i = 0; i < hashTree.size(); i++) {
JSONObject element = hashTree.getJSONObject(i);
boolean isScenarioEnv = false;
@ -357,6 +362,13 @@ public class ElementUtil {
MsHTTPSamplerProxy httpSamplerProxy = JSON.toJavaObject(element, MsHTTPSamplerProxy.class);
if (httpSamplerProxy != null
&& (!httpSamplerProxy.isCustomizeReq() || (httpSamplerProxy.isCustomizeReq() && httpSamplerProxy.getIsRefEnvironment()))) {
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
if (element != null && StringUtils.isNotEmpty(element.getString("hashTree"))) {
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
new TypeReference<LinkedList<MsTestElement>>() {
});
httpSamplerProxy.setHashTree(elements);
}
HashTree tmpHashTree = new HashTree();
httpSamplerProxy.toHashTree(tmpHashTree, null, msParameter);
if (tmpHashTree != null && tmpHashTree.getArray().length > 0) {
@ -380,4 +392,46 @@ public class ElementUtil {
LogUtil.error(e.getMessage());
}
}
public static void mergeHashTree(List<MsTestElement> sourceHashTree, List<MsTestElement> targetHashTree) {
List<String> sourceIds = new ArrayList<>();
List<String> delIds = new ArrayList<>();
Map<String, MsTestElement> updateMap = new HashMap<>();
if (CollectionUtils.isEmpty(sourceHashTree)) {
sourceHashTree.addAll(targetHashTree);
return;
}
if (CollectionUtils.isNotEmpty(targetHashTree)) {
for (MsTestElement item : targetHashTree) {
updateMap.put(item.getId(), item);
}
}
// 找出待更新内容和源已经被删除的内容
if (CollectionUtils.isNotEmpty(sourceHashTree)) {
for (int i = 0; i < sourceHashTree.size(); i++) {
MsTestElement source = sourceHashTree.get(i);
if (source != null) {
sourceIds.add(source.getId());
if (!StringUtils.equals(source.getLabel(), "SCENARIO-REF-STEP")) {
if (StringUtils.isNotEmpty(source.getId()) && updateMap.containsKey(source.getId())) {
sourceHashTree.set(i, updateMap.get(source.getId()));
} else {
delIds.add(source.getId());
}
}
}
}
}
// 删除多余的步骤
sourceHashTree.removeIf(item -> item != null && delIds.contains(item.getId()));
// 补充新增的源引用步骤
if (CollectionUtils.isNotEmpty(targetHashTree)) {
for (MsTestElement item : targetHashTree) {
if (!sourceIds.contains(item.getId())) {
sourceHashTree.add(item);
}
}
}
}
}

View File

@ -128,7 +128,11 @@ public class MsDubboSampler extends MsTestElement {
}
}
if (proxy != null) {
this.setHashTree(proxy.getHashTree());
if (StringUtils.equals(this.getRefType(), "CASE")) {
ElementUtil.mergeHashTree(this.getHashTree(), proxy.getHashTree());
} else {
this.setHashTree(proxy.getHashTree());
}
this.setMethod(proxy.getMethod());
this.set_interface(proxy.get_interface());
this.setAttachmentArgs(proxy.getAttachmentArgs());

View File

@ -154,7 +154,11 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
}
if (proxy != null) {
this.setHashTree(proxy.getHashTree());
if (StringUtils.equals(this.getRefType(), "CASE")) {
ElementUtil.mergeHashTree(this.getHashTree(), proxy.getHashTree());
}else {
this.setHashTree(proxy.getHashTree());
}
this.setMethod(proxy.getMethod());
this.setBody(proxy.getBody());
this.setRest(proxy.getRest());

View File

@ -230,7 +230,11 @@ public class MsJDBCSampler extends MsTestElement {
}
}
if (proxy != null) {
this.setHashTree(proxy.getHashTree());
if (StringUtils.equals(this.getRefType(), "CASE")) {
ElementUtil.mergeHashTree(this.getHashTree(), proxy.getHashTree());
} else {
this.setHashTree(proxy.getHashTree());
}
this.setDataSource(proxy.getDataSource());
this.setDataSourceId(proxy.getDataSourceId());
this.setQuery(proxy.getQuery());

View File

@ -182,7 +182,11 @@ public class MsTCPSampler extends MsTestElement {
}
}
if (proxy != null) {
this.setHashTree(proxy.getHashTree());
if (StringUtils.equals(this.getRefType(), "CASE")) {
ElementUtil.mergeHashTree(this.getHashTree(), proxy.getHashTree());
}else {
this.setHashTree(proxy.getHashTree());
}
this.setClassname(proxy.getClassname());
this.setServer(proxy.getServer());
this.setPort(proxy.getPort());

View File

@ -948,7 +948,7 @@ export default {
setComponent(type, this, plugin);
},
nodeClick(data, node) {
if (data.referenced != 'REF' && data.referenced != 'Deleted' && !data.disabled && this.stepFilter) {
if ((data.referenced != 'REF' && data.referenced != 'Deleted' && !data.disabled && this.stepFilter) || data.refType === 'CASE') {
this.operatingElements = this.stepFilter.get(data.type);
} else {
this.operatingElements = [];

View File

@ -2,18 +2,16 @@
<el-card :style="{'border-color':colorStyle}">
<div class="header" @click="active(data)">
<slot name="beforeHeaderLeft">
<div v-if="data.index" class="el-step__icon is-text" style="margin-right: 10px;" :style="{'color': color, 'background-color': backgroundColor}">
<div v-if="data.index" class="el-step__icon is-text enable-switch" :style="{'color': color, 'background-color': backgroundColor}">
<div class="el-step__icon-inner">{{ data.index }}</div>
</div>
<el-tag class="ms-left-btn" size="small" :style="{'color': color, 'background-color': backgroundColor}">{{ title }}</el-tag>
<el-tag size="mini" v-if="data.method && !data.pluginId">{{ getMethod() }}</el-tag>
</slot>
<slot name="behindHeaderLeft" v-if="!isMax"></slot>
<span>
<slot name="headerLeft">
<i class="icon el-icon-arrow-right" :class="{'is-active': data.active}"
@click="active(data)" v-if="data.type!='scenario' && !isMax " @click.stop/>
<i class="icon el-icon-arrow-right" :class="{'is-active': data.active}" @click="active(data)" v-if="data.type!='scenario' && !isMax " @click.stop/>
<span @click.stop v-if="isShowInput && isShowNameInput">
<el-input :draggable="draggable" size="mini" v-model="data.name" class="name-input" @focus="active(data)"
@blur="isShowInput = false" :placeholder="$t('commons.input_name')" ref="nameEdit" :disabled="data.disabled"/>

View File

@ -32,10 +32,10 @@
</template>
<template v-slot:button>
<el-tooltip :content="$t('api_test.run')" placement="top" v-if="!loading">
<el-button :disabled="!request.enable" @click="run" icon="el-icon-video-play" style="padding: 5px" class="ms-btn" size="mini" circle/>
<el-button @click="run" icon="el-icon-video-play" style="padding: 5px" class="ms-btn" size="mini" circle/>
</el-tooltip>
<el-tooltip :content="$t('report.stop_btn')" placement="top" :enterable="false" v-else>
<el-button :disabled="!request.enable" @click.once="stop" size="mini" style="color:white;padding: 0 0.1px;width: 24px;height: 24px;" class="stop-btn" circle>
<el-button @click.once="stop" size="mini" style="color:white;padding: 0 0.1px;width: 24px;height: 24px;" class="stop-btn" circle>
<div style="transform: scale(0.66)">
<span style="margin-left: -4.5px;font-weight: bold;">STOP</span>
</div>
@ -371,18 +371,15 @@ export default {
getApiInfo() {
if (this.request.id && this.request.referenced === 'REF') {
let requestResult = this.request.requestResult;
let url = this.request.refType && this.request.refType === 'CASE' ? "/api/testcase/get/" : "/api/definition/get/";
let enable = this.request.enable;
this.$get(url + this.request.id, response => {
this.$get("/api/testcase/get/" + this.request.id, response => {
if (response.data) {
Object.assign(this.request, JSON.parse(response.data.request));
this.request.name = response.data.name;
this.request.referenced = "REF";
this.request.enable = enable;
if (response.data.path && response.data.path != null) {
this.request.path = response.data.path;
this.request.url = response.data.url;
this.setUrl(this.request.path);
}
if (response.data.method && response.data.method != null) {
this.request.method = response.data.method;
@ -396,6 +393,10 @@ export default {
this.request.disabled = true;
this.request.root = true;
this.request.projectId = response.data.projectId;
let req = JSON.parse(response.data.request);
if (req && this.request) {
this.mergeHashTree(req.hashTree);
}
this.reload();
this.sort();
} else {
@ -404,22 +405,67 @@ export default {
})
}
},
sort(arr) {
if (!arr) {
arr = this.request.hashTree;
}
for (let i in arr) {
arr[i].disabled = true;
arr[i].index = Number(i) + 1;
if (!arr[i].resourceId) {
arr[i].resourceId = getUUID();
mergeHashTree(targetHashTree) {
let sourceHashTree = this.request.hashTree;
let sourceIds = [];
let delIds = [];
let updateMap = new Map();
if (!sourceHashTree || sourceHashTree.length == 0) {
if (targetHashTree) {
targetHashTree.forEach(item => {
item.disabled = true;
})
this.request.hashTree = targetHashTree;
}
if (arr[i].hashTree != undefined && arr[i].hashTree.length > 0) {
this.sort(arr[i].hashTree);
return;
}
if (targetHashTree) {
for(let i in targetHashTree){
targetHashTree[i].disabled = true;
updateMap.set(targetHashTree[i].id, targetHashTree[i]);
}
}
if (sourceHashTree && sourceHashTree.length > 0) {
for (let index in sourceHashTree) {
let source = sourceHashTree[index];
sourceIds.push(source.id);
if (source.label !== 'SCENARIO-REF-STEP') {
if (source.id && updateMap.has(source.id)) {
Object.assign(sourceHashTree[index] , updateMap.get(source.id));
sourceHashTree[index].disabled = true;
sourceHashTree[index].label = '';
sourceHashTree[index].enable = updateMap.get(source.id).enable;
} else {
delIds.push(source.id);
}
}
}
}
//
delIds.forEach(item => {
const removeIndex = sourceHashTree.findIndex(d => d.id && d.id === item);
sourceHashTree.splice(removeIndex, 1);
})
//
if (targetHashTree) {
targetHashTree.forEach(item => {
if (sourceIds.indexOf(item.id) === -1) {
item.disabled = true;
this.request.hashTree.push(item);
}
})
}
},
sort() {
for (let i in this.request.hashTree) {
this.request.hashTree[i].index = Number(i) + 1;
if (!this.request.hashTree[i].resourceId) {
this.request.hashTree[i].resourceId = getUUID();
}
}
},
active(item) {
active() {
this.request.active = !this.request.active;
if (this.node) {
this.node.expanded = this.request.active;

View File

@ -152,35 +152,35 @@ export function setComponent(type, _this, plugin) {
_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({label: "SCENARIO-REF-STEP"})) :
_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({label: "SCENARIO-REF-STEP"})) :
_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",label: "SCENARIO-REF-STEP"})) :
_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",label: "SCENARIO-REF-STEP"})) :
_this.scenarioDefinition.push(new JSR223Processor({type: "JSR223PostProcessor"}));
break;
case ELEMENT_TYPE.JDBCPreProcessor:
_this.selectedTreeNode !== undefined ? _this.selectedTreeNode.hashTree.push(new JDBCProcessor({type: "JDBCPreProcessor"})) :
_this.selectedTreeNode !== undefined ? _this.selectedTreeNode.hashTree.push(new JDBCProcessor({type: "JDBCPreProcessor",label: "SCENARIO-REF-STEP"})) :
_this.scenarioDefinition.push(new JDBCProcessor({type: "JDBCPreProcessor"}));
break;
case ELEMENT_TYPE.JDBCPostProcessor:
_this.selectedTreeNode !== undefined ? _this.selectedTreeNode.hashTree.push(new JDBCProcessor({type: "JDBCPostProcessor"})) :
_this.selectedTreeNode !== undefined ? _this.selectedTreeNode.hashTree.push(new JDBCProcessor({type: "JDBCPostProcessor",label: "SCENARIO-REF-STEP"})) :
_this.scenarioDefinition.push(new JDBCProcessor({type: "JDBCPostProcessor"}));
break;
case ELEMENT_TYPE.Assertions:
_this.selectedTreeNode !== undefined ? _this.selectedTreeNode.hashTree.push(new Assertions()) :
_this.selectedTreeNode !== undefined ? _this.selectedTreeNode.hashTree.push(new Assertions({label: "SCENARIO-REF-STEP"})) :
_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({label: "SCENARIO-REF-STEP"})) :
_this.scenarioDefinition.push(new Extract());
break;
case ELEMENT_TYPE.CustomizeReq:

View File

@ -17,6 +17,7 @@
<script>
import {createComponent} from "../../jmeter/components";
import {Assertions, Extract} from "../../../model/ApiTestModel";
import {getUUID} from "@/common/js/utils";
export default {
name: "ApiDefinitionStepButton",
@ -58,14 +59,14 @@
this.request.hashTree.push(jdbcPostProcessor);
},
addAssertions() {
let assertions = new Assertions();
let assertions = new Assertions({id:getUUID()});
if (!this.request.hashTree) {
this.request.hashTree = [];
}
this.request.hashTree.push(assertions);
},
addExtract() {
let jsonPostProcessor = new Extract();
let jsonPostProcessor = new Extract({id:getUUID()});
if (!this.request.hashTree) {
this.request.hashTree = [];
}

View File

@ -846,6 +846,7 @@ export class JSR223Processor extends BaseConfig {
this.resourceId = uuid();
this.active = false;
this.type = "JSR223Processor";
this.label="";
this.script = undefined;
this.scriptLanguage = "beanshell";
this.enable = true;