feat(系统设置): 自定义函数测试执行

This commit is contained in:
shiziyuan9527 2021-09-12 14:06:34 +08:00 committed by 刘瑞斌
parent 4ab568610b
commit 2d7d360ec4
5 changed files with 192 additions and 12 deletions

View File

@ -2,6 +2,7 @@ package io.metersphere.controller;
import com.github.pagehelper.Page; import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.base.domain.CustomFunction; import io.metersphere.base.domain.CustomFunction;
import io.metersphere.base.domain.CustomFunctionWithBLOBs; import io.metersphere.base.domain.CustomFunctionWithBLOBs;
import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.PageUtils;
@ -54,4 +55,9 @@ public class CustomFunctionController {
public CustomFunctionWithBLOBs get(@PathVariable String id) { public CustomFunctionWithBLOBs get(@PathVariable String id) {
return customFunctionService.get(id); return customFunctionService.get(id);
} }
@PostMapping("/run")
public String run(@RequestBody RunDefinitionRequest request) {
return customFunctionService.run(request);
}
} }

View File

@ -1,15 +1,20 @@
package io.metersphere.service; package io.metersphere.service;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.base.domain.CustomFunction; import io.metersphere.base.domain.CustomFunction;
import io.metersphere.base.domain.CustomFunctionExample; import io.metersphere.base.domain.CustomFunctionExample;
import io.metersphere.base.domain.CustomFunctionWithBLOBs; import io.metersphere.base.domain.CustomFunctionWithBLOBs;
import io.metersphere.base.mapper.CustomFunctionMapper; import io.metersphere.base.mapper.CustomFunctionMapper;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.CustomFunctionRequest; import io.metersphere.controller.request.CustomFunctionRequest;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -28,6 +33,8 @@ public class CustomFunctionService {
@Resource @Resource
private CustomFunctionMapper customFunctionMapper; private CustomFunctionMapper customFunctionMapper;
@Resource
private JMeterService jMeterService;
public CustomFunctionWithBLOBs save(CustomFunctionRequest request) { public CustomFunctionWithBLOBs save(CustomFunctionRequest request) {
request.setId(UUID.randomUUID().toString()); request.setId(UUID.randomUUID().toString());
@ -106,4 +113,13 @@ public class CustomFunctionService {
} }
return customFunctionMapper.selectByPrimaryKey(id); return customFunctionMapper.selectByPrimaryKey(id);
} }
public String run(RunDefinitionRequest request) {
ParameterConfig config = new ParameterConfig();
config.setProjectId(request.getProjectId());
HashTree hashTree = request.getTestElement().generateHashTree(config);
String runMode = ApiRunMode.DEFINITION.name();
jMeterService.runLocal(request.getId(), hashTree, request.getReportId(), runMode);
return request.getId();
}
} }

View File

@ -49,25 +49,34 @@
ref="codeEdit"/> ref="codeEdit"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="'执行结果'" name="result"> <el-tab-pane :label="'执行结果'" name="result">
执行结果 <div v-loading="runResult.loading">
<ms-code-edit :mode="'text'" :data.sync="console" v-if="isResultAlive" height="330px" ref="funcResult"/>
</div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</template> </template>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="4" class="script-index"> <el-col :span="4" class="script-index">
<ms-dropdown :default-command="form.type" :commands="languages" @command="languageChange"/> <div style="margin-top: -25px; margin-left: 10px;">
<div class="template-title">{{ $t('api_test.request.processor.code_template') }}</div> <div style="margin-bottom: 10px;">
<div v-for="(template, index) in codeTemplates" :key="index" class="code-template"> <el-button type="primary" size="mini" style="width: 70px;" @click="handleTest" :disabled="runResult.loading">测试</el-button>
<el-link :disabled="template.disabled" @click="addTemplate(template)">{{ template.title }}</el-link> </div>
<ms-dropdown :default-command="form.type" :commands="languages" @command="languageChange"/>
<div class="template-title">{{ $t('api_test.request.processor.code_template') }}</div>
<div v-for="(template, index) in codeTemplates" :key="index" class="code-template">
<el-link :disabled="template.disabled" @click="addTemplate(template)">{{ template.title }}</el-link>
</div>
<el-link href="https://jmeter.apache.org/usermanual/component_reference.html#BeanShell_PostProcessor"
target="componentReferenceDoc" style="margin-top: 10px"
type="primary">{{ $t('commons.reference_documentation') }}
</el-link>
</div> </div>
<el-link href="https://jmeter.apache.org/usermanual/component_reference.html#BeanShell_PostProcessor"
target="componentReferenceDoc" style="margin-top: 10px"
type="primary">{{ $t('commons.reference_documentation') }}
</el-link>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<!-- 执行组件 -->
<function-run :report-id="reportId" :run-data="runData" @runRefresh="runRefresh" @errorRefresh="errorRefresh"/>
</div> </div>
<template v-slot:footer> <template v-slot:footer>
<el-button @click="close" size="medium">{{ $t('commons.cancel') }}</el-button> <el-button @click="close" size="medium">{{ $t('commons.cancel') }}</el-button>
@ -84,25 +93,38 @@ import FunctionParams from "@/business/components/settings/project/function/Func
import MsCodeEdit from "@/business/components/common/components/MsCodeEdit"; import MsCodeEdit from "@/business/components/common/components/MsCodeEdit";
import MsDropdown from "@/business/components/common/components/MsDropdown"; import MsDropdown from "@/business/components/common/components/MsDropdown";
import {splicingCustomFunc} from "@/business/components/settings/project/function/custom_function"; import {splicingCustomFunc} from "@/business/components/settings/project/function/custom_function";
import MsRun from "@/business/components/api/automation/scenario/DebugRun";
import {getUUID} from "@/common/js/utils";
import {JSR223Processor} from "@/business/components/api/definition/model/ApiTestModel";
import FunctionRun from "@/business/components/settings/project/function/FunctionRun";
export default { export default {
name: "EditFunction", name: "EditFunction",
components: { components: {
FunctionRun,
MsCodeEdit, MsCodeEdit,
FunctionParams, FunctionParams,
MsInputTag, MsInputTag,
MsDropdown MsDropdown,
MsRun
}, },
props: {}, props: {},
data() { data() {
return { return {
visible: false, visible: false,
result: {}, result: {},
runResult: {
loading: false
},
reportId: "",
runData: [],
isStop: false,
dialogCreateTitle: "创建函数", dialogCreateTitle: "创建函数",
dialogUpdateTitle: "更新函数", dialogUpdateTitle: "更新函数",
activeName: 'code', activeName: 'code',
dialogTitle: "", dialogTitle: "",
isCodeEditAlive: true, isCodeEditAlive: true,
isResultAlive: true,
isFormAlive: true, isFormAlive: true,
form: { form: {
params: [], params: [],
@ -179,6 +201,10 @@ export default {
disabled: this.isPreProcessor disabled: this.isPreProcessor
} }
], ],
response: {},
request: {},
debug: true,
console: "无执行结果"
} }
}, },
watch: { watch: {
@ -204,6 +230,7 @@ export default {
params.join(",\s"); params.join(",\s");
} }
} }
// todo
return params; return params;
}, },
splicingFunc() { splicingFunc() {
@ -215,6 +242,7 @@ export default {
this.reloadCodeEdit(); this.reloadCodeEdit();
}, },
open(data) { open(data) {
this.activeName = "code";
this.visible = true; this.visible = true;
this.form.type = "beanshell"; this.form.type = "beanshell";
if (data && data.id) { if (data && data.id) {
@ -248,6 +276,7 @@ export default {
type: "beanshell", type: "beanshell",
params: [{}] params: [{}]
}; };
this.console = "无执行结果";
this.visible = false; this.visible = false;
}, },
languageChange(language) { languageChange(language) {
@ -274,6 +303,10 @@ export default {
this.isCodeEditAlive = false; this.isCodeEditAlive = false;
this.$nextTick(() => (this.isCodeEditAlive = true)); this.$nextTick(() => (this.isCodeEditAlive = true));
}, },
reloadResult() {
this.isResultAlive = false;
this.$nextTick(() => (this.isResultAlive = true));
},
submit() { submit() {
let param = Object.assign({}, this.form); let param = Object.assign({}, this.form);
param.params = JSON.stringify(this.form.params); param.params = JSON.stringify(this.form.params);
@ -298,6 +331,29 @@ export default {
this.$emit("refresh"); this.$emit("refresh");
this.$success(this.$t('commons.modify_success')); this.$success(this.$t('commons.modify_success'));
}) })
},
handleTest() {
this.activeName = "result";
this.console = "无执行结果";
this.reloadResult();
this.runResult.loading = true;
let jSR223Processor = new JSR223Processor({
script: this.form.script
});
jSR223Processor.id = getUUID().substring(0, 8);
this.runData = [];
this.runData.push(jSR223Processor);
this.reportId = getUUID().substring(0, 8);
},
runRefresh(data) {
this.response = data;
this.console = this.response.responseResult.console;
this.runResult.loading = false;
this.reloadResult();
},
errorRefresh() {
this.runResult.loading = false;
} }
} }
} }
@ -308,6 +364,7 @@ export default {
margin-bottom: 5px; margin-bottom: 5px;
font-weight: bold; font-weight: bold;
font-size: 15px; font-size: 15px;
margin-top: 1px;
} }
/* 滚动条样式 */ /* 滚动条样式 */

View File

@ -0,0 +1,101 @@
<template>
<span></span>
</template>
<script>
import {getCurrentProjectID, strMapToObj} from "@/common/js/utils";
import {TYPE_TO_C} from "@/business/components/api/automation/scenario/Setting";
import TestPlan from "@/business/components/api/definition/components/jmeter/components/test-plan";
import ThreadGroup from "@/business/components/api/definition/components/jmeter/components/thread-group";
export default {
name: 'FunctionRun',
components: {},
props: {
environment: Object,
debug: Boolean,
reportId: String,
runData: Array,
type: String,
envMap: Map,
isStop: Boolean,
},
data() {
return {
result: {},
loading: false,
runId: "",
reqNumber: 0,
websocket: {}
}
},
watch: {
//
reportId() {
this.run()
},
isStop() {
if (!this.isStop && this.websocket && this.websocket.close instanceof Function) {
this.websocket.close();
}
}
},
methods: {
initWebSocket() {
let protocol = "ws://";
if (window.location.protocol === 'https:') {
protocol = "wss://";
}
const uri = protocol + window.location.host + "/api/definition/run/report/" + this.runId + "/debug";
this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage;
},
onMessage(e) {
if (e.data) {
let data = JSON.parse(e.data);
this.websocket.close();
this.$emit('runRefresh', data);
}
},
sort(stepArray) {
if (stepArray) {
for (let i in stepArray) {
if (!stepArray[i].clazzName) {
stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type);
}
if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) {
this.sort(stepArray[i].hashTree);
}
}
}
},
run() {
let projectId = getCurrentProjectID();
let testPlan = new TestPlan();
testPlan.clazzName = TYPE_TO_C.get(testPlan.type);
let threadGroup = new ThreadGroup();
threadGroup.clazzName = TYPE_TO_C.get(threadGroup.type);
threadGroup.hashTree = [];
testPlan.hashTree = [threadGroup];
this.runData.forEach(item => {
item.projectId = projectId;
if (!item.clazzName) {
item.clazzName = TYPE_TO_C.get(item.type);
}
threadGroup.hashTree.push(item);
})
this.sort(testPlan.hashTree);
let reqObj = {id: this.reportId, testElement: testPlan, type: this.type, clazzName: this.clazzName ? this.clazzName : TYPE_TO_C.get(this.type), projectId: projectId, environmentMap: strMapToObj(this.envMap)};
let url = "/custom/func/run";
reqObj.reportId = this.reportId;
this.$post(url, reqObj, res => {
this.runId = res.data;
this.initWebSocket();
}, () => {
this.$emit('errorRefresh', {});
})
}
}
}
</script>

View File

@ -11,7 +11,7 @@ export function generateFuncFirstLine(funcLanguage, funcName, funcParams) {
let funcFirstLine = ""; let funcFirstLine = "";
switch (funcLanguage) { switch (funcLanguage) {
case "beanshell": case "beanshell":
funcFirstLine = "function " + funcName + "(" + funcParams + ") " + "{"; funcFirstLine = "public static void " + funcName + "(" + funcParams + ") " + "{";
break; break;
case "python": case "python":
break; break;
@ -27,7 +27,7 @@ export function generateFuncFirstLine(funcLanguage, funcName, funcParams) {
} }
const regex = { const regex = {
beanshell: /^function\s.*\(.*\)\s\{/, beanshell: /^public static void\s.*\(.*\)\s\{/,
python: /^function\s.*\(.*\)\s\{/, python: /^function\s.*\(.*\)\s\{/,
groovy: /^function\s.*\(.*\)\s\{/, groovy: /^function\s.*\(.*\)\s\{/,
nashornScript: /^function\s.*\(.*\)\s\{/, nashornScript: /^function\s.*\(.*\)\s\{/,