Merge remote-tracking branch 'origin/master'

This commit is contained in:
Captain.B 2021-03-24 17:51:59 +08:00
commit 4b9b9f4953
15 changed files with 118 additions and 73 deletions

View File

@ -14,9 +14,9 @@ import java.util.List;
public class SwaggerApiExportResult extends ApiExportResult{
private String openapi;
private SwaggerInfo info;
private String externalDocs;
private JSONObject externalDocs;
private List<String> servers;
private List<SwaggerTag> tags;
private JSONObject paths; // Map<String, Object>, Object 里放 Operation 对象
private List<String> components;
private JSONObject components;
}

View File

@ -231,6 +231,9 @@ public class Swagger3Parser extends SwaggerAbstractParser {
MediaType mediaType = content.get(contentType);
if (mediaType == null) {
Set<String> contentTypes = content.keySet();
if(contentTypes.size() == 0) { // 防止空指针
return;
}
contentType = contentTypes.iterator().next();
if (StringUtils.isBlank(contentType)) {
return;
@ -410,26 +413,34 @@ public class Swagger3Parser extends SwaggerAbstractParser {
result.setInfo(new SwaggerInfo());
result.setServers(new ArrayList<>());
result.setTags(new ArrayList<>());
result.setComponents(new ArrayList<>());
result.setComponents(new JSONObject());
result.setExternalDocs(new JSONObject());
JSONObject paths = new JSONObject();
JSONObject swaggerPath = new JSONObject();
for(ApiDefinitionWithBLOBs apiDefinition : apiDefinitionList) {
SwaggerApiInfo swaggerApiInfo = new SwaggerApiInfo(); // {tags:, summary:, description:, parameters:}
swaggerApiInfo.setSummary(apiDefinition.getName());
// 设置导入后的模块名 根据 api moduleID 查库获得所属模块作为导出的模块名
ApiModuleService apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
String moduleName = apiModuleService.getNode(apiDefinition.getModuleId()).getName();
String moduleName = "";
if(apiDefinition.getModuleId() != null) { // module_id 可能为空
moduleName = apiModuleService.getNode(apiDefinition.getModuleId()).getName();
}
swaggerApiInfo.setTags(Arrays.asList(moduleName));
// 设置请求体
JSONObject requestObject = JSON.parseObject(apiDefinition.getRequest()); // 将api的request属性转换成JSON对象以便获得参数
JSONObject requestBody = buildRequestBody(requestObject);
swaggerApiInfo.setRequestBody(requestBody);
// 设置响应体
swaggerApiInfo.setResponses(new JSONObject());
// 设置请求参数列表
List<JSONObject> paramsList = buildParameters(requestObject);
swaggerApiInfo.setParameters(paramsList);
swaggerPath.put(apiDefinition.getMethod().toLowerCase(), JSON.parseObject(JSON.toJSONString(swaggerApiInfo))); // 设置api的请求类型和api定义参数
paths.put(apiDefinition.getPath(), swaggerPath);
JSONObject methodDetail = JSON.parseObject(JSON.toJSONString(swaggerApiInfo));
if(paths.getJSONObject(apiDefinition.getPath()) == null) {
paths.put(apiDefinition.getPath(), new JSONObject());
} // 一个路径下有多个发方法如postget因此是一个 JSONObject 类型
paths.getJSONObject(apiDefinition.getPath()).put(apiDefinition.getMethod().toLowerCase(), methodDetail);
}
result.setPaths(paths);
return result;
@ -477,7 +488,9 @@ public class Swagger3Parser extends SwaggerAbstractParser {
schema.put("format", null);
typeName.put("schema", schema);
JSONObject content = new JSONObject();
if (type != null && StringUtils.isNotBlank(type)) {
content.put(typeMap.get(type), typeName);
}
requestBody.put("content", content);
return requestBody;
}

View File

@ -14,4 +14,5 @@ public class SwaggerApiInfo {
private String summary; // 对应 API 的名字
private List<JSONObject> parameters; // 对应 API 的请求参数
private JSONObject requestBody;
private JSONObject responses;
}

View File

@ -140,7 +140,7 @@ public class MsScenario extends MsTestElement {
if (arguments != null) {
tree.add(config.valueSupposeMock(arguments));
}
this.addCsvDataSet(tree, variables);
this.addCsvDataSet(tree, variables,config);
this.addCounter(tree, variables);
this.addRandom(tree, variables);
if (CollectionUtils.isNotEmpty(this.headers)) {

View File

@ -201,7 +201,7 @@ public abstract class MsTestElement {
return null;
}
protected void addCsvDataSet(HashTree tree, List<ScenarioVariable> variables) {
protected void addCsvDataSet(HashTree tree, List<ScenarioVariable> variables,ParameterConfig config) {
if (CollectionUtils.isNotEmpty(variables)) {
List<ScenarioVariable> list = variables.stream().filter(ScenarioVariable::isCSVValid).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(list)) {
@ -213,7 +213,7 @@ public abstract class MsTestElement {
csvDataSet.setName(StringUtils.isEmpty(item.getName()) ? "CSVDataSet" : item.getName());
csvDataSet.setProperty("fileEncoding", StringUtils.isEmpty(item.getEncoding()) ? "UTF-8" : item.getEncoding());
if (CollectionUtils.isNotEmpty(item.getFiles())) {
if (new File(BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName()).exists()) {
if (!config.isOperating() && new File(BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName()).exists()) {
MSException.throwException(StringUtils.isEmpty(item.getName()) ? "CSVDataSet" : item.getName() + "[ CSV文件不存在 ]");
}
csvDataSet.setProperty("filename", BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName());

View File

@ -56,7 +56,7 @@ public class MsLoopController extends MsTestElement {
}
final HashTree groupTree = controller(tree);
if (CollectionUtils.isNotEmpty(config.getVariables())) {
this.addCsvDataSet(groupTree, config.getVariables());
this.addCsvDataSet(groupTree, config.getVariables(),config);
this.addCounter(groupTree, config.getVariables());
this.addRandom(groupTree, config.getVariables());
}

View File

@ -58,7 +58,7 @@ public class MsJmeterElement extends MsTestElement {
LogUtil.info(((ThreadGroup) scriptWrapper).getName() + "是被禁用线程组不加入执行");
} else {
// CSV数据检查文件路径是否还存在
if (scriptWrapper instanceof CSVDataSet) {
if (!config.isOperating() && scriptWrapper instanceof CSVDataSet) {
String path = ((CSVDataSet) scriptWrapper).getPropertyAsString("filename");
if (!new File(path).exists()) {
MSException.throwException(StringUtils.isEmpty(((CSVDataSet) scriptWrapper).getName()) ? "CSVDataSet" : ((CSVDataSet) scriptWrapper).getName() + "[ CSV文件不存在 ]");

View File

@ -1,6 +1,6 @@
<template>
<div>
<el-card class="table-card" v-loading="loading">
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="selectByParam" title=""
:show-create="false" :tip="$t('commons.search_by_id_name_tag')"/>
@ -246,10 +246,10 @@
},
data() {
return {
result: {},
type: API_SCENARIO_LIST,
headerItems: Api_Scenario_List,
tableLabel: Api_Scenario_List,
loading: false,
screenHeight: document.documentElement.clientHeight - 280,//,
condition: {
components: API_SCENARIO_CONFIGS
@ -358,7 +358,7 @@
this.search();
},
batchReportId() {
this.loading = true;
this.result.loading = true;
this.getReport();
}
},
@ -418,7 +418,7 @@
this.selectDataCounts = 0;
let url = "/api/automation/list/" + this.currentPage + "/" + this.pageSize;
if (this.condition.projectId) {
this.loading = true;
this.result.loading = true;
this.$post(url, this.condition, response => {
let data = response.data;
this.total = data.itemCount;
@ -428,7 +428,7 @@
item.tags = JSON.parse(item.tags);
}
});
this.loading = false;
this.result.loading = false;
this.unSelection = data.listObject.map(s => s.id);
if (this.$refs.scenarioTable) {
this.$refs.scenarioTable.doLayout()
@ -550,13 +550,13 @@
} catch (e) {
throw e;
}
this.loading = false;
this.result.loading = false;
this.$success("批量执行成功,请到报告页面查看详情!");
} else {
setTimeout(this.getReport, 2000)
}
} else {
this.loading = false;
this.result.loading = false;
this.$error(this.$t('api_report.not_exist'));
}
});
@ -723,9 +723,9 @@
this.$warning(this.$t("api_test.automation.scenario.check_case"));
return;
}
this.loading = true;
this.result.loading = true;
this.result = this.$post("/api/automation/export", param, response => {
this.loading = false;
this.result.loading = false;
let obj = response.data;
this.buildApiPath(obj.data);
downloadFile("Metersphere_Scenario_" + localStorage.getItem(PROJECT_NAME) + ".json", JSON.stringify(obj));
@ -738,9 +738,9 @@
this.$warning(this.$t("api_test.automation.scenario.check_case"));
return;
}
this.loading = true;
this.result.loading = true;
this.result = this.$post("/api/automation/export/jmx", param, response => {
this.loading = false;
this.result.loading = false;
let obj = response.data;
if (obj && obj.length > 0) {
obj.forEach(item => {

View File

@ -20,28 +20,29 @@
<template v-slot:append>
<el-dropdown v-if="!isReadOnly" size="small" split-button type="primary" class="ms-api-button" @click="handleCommand('add-api')"
v-tester
@command="handleCommand">
@command="handleCommand" trigger="click">
<el-button icon="el-icon-folder-add" @click="addScenario"></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="add-scenario">{{ $t('api_test.automation.add_scenario') }}</el-dropdown-item>
<el-dropdown-item command="import">{{ $t('api_test.api_import.label') }}</el-dropdown-item>
<el-dropdown-item command="export">{{ $t('report.export') }}MS</el-dropdown-item>
<el-dropdown-item command="exportJmx">{{ $t('report.export') }}JMX</el-dropdown-item>
<el-dropdown-item command="exports">
<el-dropdown placement="right-start" @command="chooseExportType">
<span>
{{ $t('report.export') }} <i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<template>
<el-dropdown-item command="export">{{ $t('report.export_to_ms_format') }}</el-dropdown-item>
<el-dropdown-item command="exportJmx">{{ $t('report.export') }} JMETER 格式</el-dropdown-item>
</template>
</el-dropdown-menu>
</el-dropdown>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-input>
<module-trash-button v-if="!isReadOnly" :condition="condition" :exe="enableTrash"/>
<!-- 是否保留这个 -->
<!--<api-scenario-module-header-->
<!--:condition="condition"-->
<!--:current-module="currentModule"-->
<!--:is-read-only="isReadOnly"-->
<!--:project-id="projectId"-->
<!--@exportAPI="exportAPI"-->
<!--@addScenario="addScenario"-->
<!--@refreshTable="$emit('refreshTable')"-->
<!--@refresh="refresh"/>-->
</template>
</ms-node-tree>
@ -150,6 +151,16 @@
break;
}
},
chooseExportType(e) {
switch (e) {
case "export":
this.$emit('exportAPI');
break;
case "exportJmx":
this.$emit('exportJmx');
break;
}
},
list(projectId) {
let url = undefined;
if (this.isPlanModel) {

View File

@ -28,7 +28,7 @@
<div class="header-right" @click.stop>
<slot name="message"></slot>
<el-tooltip :content="$t('test_resource_pool.enable_disable')" placement="top" v-if="showBtn">
<el-switch v-model="data.enable" class="enable-switch" size="mini" :disabled="data.disabled"/>
<el-switch v-model="data.enable" class="enable-switch" size="mini" :disabled="data.disabled && !data.root"/>
</el-tooltip>
<slot name="button"></slot>
<step-extend-btns style="display: contents" :data="data" @copy="copyRow" @remove="remove" @openScenario="openScenario" v-if="showBtn && (!data.disabled || data.root)"/>

View File

@ -162,6 +162,7 @@
.script-content {
height: calc(100vh - 570px);
min-height: 250px;
}
.script-index {

View File

@ -1,5 +1,6 @@
<template>
<el-dialog :close-on-click-modal="false" width="60%" class="schedule-edit" :visible.sync="dialogVisible" :append-to-body='true'
<el-dialog :close-on-click-modal="false" width="60%" class="schedule-edit" :visible.sync="dialogVisible"
:append-to-body='true'
@close="close">
<template>
<div>
@ -18,12 +19,10 @@
</el-button>
</el-col>
<el-col :span="6">
<schedule-switch :schedule="schedule" @scheduleChange="scheduleChange"></schedule-switch>
<schedule-switch :schedule="schedule" :corn-value="form.cronValue" @resultListChange="getExecuteTimeTemplate" @scheduleChange="scheduleChange"></schedule-switch>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-link :disabled="isReadOnly" type="primary" @click="showCronDialog">
@ -66,7 +65,13 @@ const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./
export default {
name: "MsScheduleMaintain",
components: {CrontabResult, ScheduleSwitch,Crontab, MsScheduleNotification, "NoticeTemplate": noticeTemplate.default},
components: {
CrontabResult,
ScheduleSwitch,
Crontab,
MsScheduleNotification,
"NoticeTemplate": noticeTemplate.default
},
props: {
customValidate: {
@ -111,7 +116,7 @@ export default {
form: {
cronValue: ""
},
paramRow:{},
paramRow: {},
activeName: 'first',
rules: {
cronValue: [{required: true, validator: validateCron, trigger: 'blur'}],
@ -122,19 +127,19 @@ export default {
currentUser: () => {
return getCurrentUser();
},
scheduleChange(){
scheduleChange() {
let flag = this.schedule.enable;
let param = {};
param.taskID = this.schedule.id;
param.enable = flag;
let that = this;
if(flag == false) {
if (flag == false) {
this.$confirm(this.$t('api_test.home_page.running_task_list.confirm.close_title'), this.$t('commons.prompt'), {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning',
beforeClose(action, instance, done) {
if(action == 'cancel') { // messageBox switch
if (action == 'cancel') { // messageBox switch
that.schedule.enable = param.enable = true;
}
done(); // done messageBox
@ -143,12 +148,11 @@ export default {
this.updateTask(param);
}).catch(() => {
});
}
else {
} else {
this.updateTask(param);
}
},
updateTask(param){
updateTask(param) {
this.result = this.$post('/api/schedule/updateEnableByPrimyKey', param, response => {
let paramTestId = "";
if (this.paramRow.redirectFrom == 'testPlan') {
@ -203,7 +207,7 @@ export default {
findSchedule() {
var scheduleResourceID = this.testId;
var taskType = this.scheduleTaskType;
this.result = this.$get("/schedule/findOne/" + scheduleResourceID + "/" +taskType, response => {
this.result = this.$get("/schedule/findOne/" + scheduleResourceID + "/" + taskType, response => {
if (response.data != null) {
this.schedule = response.data;
} else {
@ -240,14 +244,14 @@ export default {
param = this.schedule;
param.resourceId = this.testId;
let url = '/api/automation/schedule/create';
if(this.scheduleTaskType === "TEST_PLAN_TEST"){
if (this.scheduleTaskType === "TEST_PLAN_TEST") {
param.scheduleFrom = "testPlan";
//
url = '/schedule/create';
if (param.id) {
url = '/schedule/update';
}
}else {
} else {
param.scheduleFrom = "scenario";
if (param.id) {
url = '/api/automation/schedule/update';
@ -297,7 +301,9 @@ export default {
let time2 = new Date(resultList[1]);
return time2 - time1;
},
getExecuteTimeTemplate(executeTileArr){
alert(executeTileArr);
},
},
computed: {
isTesterPermission() {

View File

@ -5,8 +5,6 @@
<i class="el-icon-date" size="small"></i>
<span class="character">SCHEDULER</span>
</span>
<!-- <el-switch :disabled="!schedule.value || isReadOnly" v-model="schedule.enable" @change="scheduleChange"/>-->
<!-- <el-switch :disabled="!schedule.value || isReadOnly" v-model="schedule.enable" />-->
<el-switch :disabled="!schedule.value" v-model="schedule.enable" @change="scheduleChange"/>
</div>
<div>
@ -14,7 +12,7 @@
{{ $t('schedule.next_execution_time') }}
<span :class="{'disable-character': !schedule.enable}"
v-if="!schedule.enable">{{ $t('schedule.not_set') }}</span>
<crontab-result v-if="schedule.enable" :enable-simple-mode="true" :ex="schedule.value" ref="crontabResult"/>
<crontab-result v-if="schedule.enable" :enable-simple-mode="true" :ex="cornValue" ref="crontabResult"/>
</span>
</div>
</div>
@ -33,6 +31,7 @@ export default {
props: {
testId: String,
schedule: Object,
cornValue:String,
isReadOnly: {
type: Boolean,
default: false

View File

@ -4,7 +4,7 @@
{{ description }}
</span>
<el-row>
<el-checkbox v-model="isSelectAll" v-if="items.length > 1"/>
<el-checkbox v-model="isSelectAll" v-if="isShowEnable === true && items.length > 1"/>
</el-row>
<div class="kv-row item" v-for="(item, index) in items" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
@ -52,7 +52,6 @@
valuePlaceholder: String,
isShowEnable: {
type: Boolean,
default: true
},
description: String,
items: Array,

View File

@ -2,19 +2,24 @@
<el-dialog class="user-casecader" :title="title" :visible.sync="dialogVisible"
@close="close">
<div class="block" >
<!-- <el-row>-->
<!-- <span class="demonstration" v-html="lable"></span>-->
<!-- </el-row>-->
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item prop="workspace" label-width="0px">
<el-cascader
:options="options"
<!-- <el-cascader-->
<!-- :options="options"-->
<!-- :props="props"-->
<!-- v-model="selectedIds"-->
<!-- ref="cascaderSelector"-->
<!-- style="width:100%;"-->
<!-- :key="isResouceShow"-->
<!-- clearable></el-cascader>-->
<el-cascader-panel :options="options"
:props="props"
v-model="selectedIds"
ref="cascaderSelector"
style="width:100%"
style="width:100%;"
:key="isResouceShow"
clearable></el-cascader>
clearable></el-cascader-panel>
</el-form-item>
</el-form>
</div>
@ -37,7 +42,7 @@ export default {
components: {ElUploadList, MsTableButton,MsDialogFooter},
data() {
var validateSelect = (rule, value, callback) => {
let checkNodes = this.$refs.cascaderSelector.checkedNodes;
let checkNodes = this.$refs.cascaderSelector.getCheckedNodes(true);
if(checkNodes.length==0){
callback(new Error(this.$t('workspace.select')));
}
@ -95,7 +100,7 @@ export default {
confirm(){
this.$refs.ruleForm.validate((valid) => {
if (valid) {
let checkNodes = this.$refs.cascaderSelector.checkedNodes;
let checkNodes = this.$refs.cascaderSelector.getCheckedNodes(true);
let selectValueArr = [];
for (let i = 0; i < checkNodes.length; i++) {
selectValueArr.push(checkNodes[i].value);
@ -116,9 +121,19 @@ export default {
<style scoped>
.user-casecader >>> .el-dialog {
width: 400px;
width: 600px;
}
/deep/ .el-form-item__content{
margin-left: 0px;
}
/*.el-cascader-menu {*/
/* height: 300px;*/
/*}*/
/*.el-cascader >>> .el-input--suffix {*/
/* max-height: 200px;*/
/*}*/
/*.el-cascader >>> .el-cascader__tags {*/
/* max-height: 190px;*/
/* overflow: auto;*/
/*}*/
</style>