# Conflicts:
#	frontend/src/business/components/api/test/ApiTestConfig.vue
This commit is contained in:
fit2-zhao 2020-09-07 13:44:40 +08:00
commit 20f89c0017
20 changed files with 852 additions and 506 deletions

View File

@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
@RestController @RestController
@ -44,6 +45,11 @@ public class APITestController {
return PageUtils.setPageInfo(page, apiTestService.list(request)); return PageUtils.setPageInfo(page, apiTestService.list(request));
} }
@PostMapping("/list/ids")
public List<ApiTest> listByIds(@RequestBody QueryAPITestRequest request) {
return apiTestService.listByIds(request);
}
@GetMapping("/list/{projectId}") @GetMapping("/list/{projectId}")
public List<ApiTest> list(@PathVariable String projectId) { public List<ApiTest> list(@PathVariable String projectId) {
return apiTestService.getApiTestByProjectId(projectId); return apiTestService.getApiTestByProjectId(projectId);
@ -94,6 +100,7 @@ public class APITestController {
public String runDebug(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public String runDebug(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
return apiTestService.runDebug(request, file, bodyFiles); return apiTestService.runDebug(request, file, bodyFiles);
} }
@PostMapping(value = "/checkName") @PostMapping(value = "/checkName")
public void checkName(@RequestBody SaveAPITestRequest request) { public void checkName(@RequestBody SaveAPITestRequest request) {
apiTestService.checkName(request); apiTestService.checkName(request);

View File

@ -12,6 +12,7 @@ import java.util.Map;
public class QueryAPITestRequest { public class QueryAPITestRequest {
private String id; private String id;
private String excludeId;
private String projectId; private String projectId;
private String name; private String name;
private String workspaceId; private String workspaceId;
@ -19,4 +20,5 @@ public class QueryAPITestRequest {
private List<OrderRequest> orders; private List<OrderRequest> orders;
private Map<String, List<String>> filters; private Map<String, List<String>> filters;
private Map<String, Object> combine; private Map<String, Object> combine;
private List<String> ids;
} }

View File

@ -7,6 +7,7 @@ import java.util.List;
@Data @Data
public class Scenario { public class Scenario {
private String id;
private String name; private String name;
private String url; private String url;
private String environmentId; private String environmentId;

View File

@ -23,6 +23,8 @@ public class DubboRequest implements Request {
// type 必须放最前面以便能够转换正确的类 // type 必须放最前面以便能够转换正确的类
private String type = RequestType.DUBBO; private String type = RequestType.DUBBO;
@JSONField(ordinal = 1) @JSONField(ordinal = 1)
private String id;
@JSONField(ordinal = 1)
private String name; private String name;
@JSONField(ordinal = 2) @JSONField(ordinal = 2)
private String protocol; private String protocol;

View File

@ -20,6 +20,8 @@ public class HttpRequest implements Request {
// type 必须放最前面以便能够转换正确的类 // type 必须放最前面以便能够转换正确的类
private String type = RequestType.HTTP; private String type = RequestType.HTTP;
@JSONField(ordinal = 1) @JSONField(ordinal = 1)
private String id;
@JSONField(ordinal = 1)
private String name; private String name;
@JSONField(ordinal = 2) @JSONField(ordinal = 2)
private String url; private String url;

View File

@ -74,12 +74,16 @@ public class APITestService {
return extApiTestMapper.list(request); return extApiTestMapper.list(request);
} }
public List<ApiTest> listByIds(QueryAPITestRequest request) {
return extApiTestMapper.listByIds(request.getIds());
}
public void create(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) { public void create(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
if (file == null) { if (file == null) {
throw new IllegalArgumentException(Translator.get("file_cannot_be_null")); throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
} }
checkQuota(); checkQuota();
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()) ; List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
request.setBodyUploadIds(null); request.setBodyUploadIds(null);
ApiTest test = createTest(request); ApiTest test = createTest(request);
createBodyFiles(test, bodyUploadIds, bodyFiles); createBodyFiles(test, bodyUploadIds, bodyFiles);
@ -92,7 +96,7 @@ public class APITestService {
} }
deleteFileByTestId(request.getId()); deleteFileByTestId(request.getId());
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()) ; List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
request.setBodyUploadIds(null); request.setBodyUploadIds(null);
ApiTest test = updateTest(request); ApiTest test = updateTest(request);
createBodyFiles(test, bodyUploadIds, bodyFiles); createBodyFiles(test, bodyUploadIds, bodyFiles);
@ -245,6 +249,7 @@ public class APITestService {
MSException.throwException(Translator.get("load_test_already_exists")); MSException.throwException(Translator.get("load_test_already_exists"));
} }
} }
public void checkName(SaveAPITestRequest request) { public void checkName(SaveAPITestRequest request) {
ApiTestExample example = new ApiTestExample(); ApiTestExample example = new ApiTestExample();
example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId()); example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId());
@ -411,7 +416,7 @@ public class APITestService {
} }
updateTest(request); updateTest(request);
APITestResult apiTest = get(request.getId()); APITestResult apiTest = get(request.getId());
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()) ; List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
request.setBodyUploadIds(null); request.setBodyUploadIds(null);
createBodyFiles(apiTest, bodyUploadIds, bodyFiles); createBodyFiles(apiTest, bodyUploadIds, bodyFiles);
if (SessionUtils.getUser() == null) { if (SessionUtils.getUser() == null) {

View File

@ -11,4 +11,6 @@ public interface ExtApiTestMapper {
List<APITestResult> list(@Param("request") QueryAPITestRequest request); List<APITestResult> list(@Param("request") QueryAPITestRequest request);
List<ApiTest> getApiTestByProjectId(String projectId); List<ApiTest> getApiTestByProjectId(String projectId);
List<ApiTest> listByIds(@Param("ids") List<String> ids);
} }

View File

@ -105,6 +105,9 @@
</include> </include>
</if> </if>
<if test="request.excludeId != null">
and api_test.id != #{request.excludeId}
</if>
<if test="request.name != null"> <if test="request.name != null">
and api_test.name like CONCAT('%', #{request.name},'%') and api_test.name like CONCAT('%', #{request.name},'%')
</if> </if>
@ -143,4 +146,17 @@
where project_id = #{projectId} where project_id = #{projectId}
</select> </select>
<select id="listByIds" resultType="io.metersphere.base.domain.ApiTest">
select *
from api_test
<where>
<if test="ids != null and ids.size() > 0">
id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
</where>
</select>
</mapper> </mapper>

@ -1 +1 @@
Subproject commit b86032cbbda9a9e6028308aa95a887cff2192f1c Subproject commit ecb30d83c575c6ed14adb1a1ebea389730f410a9

View File

@ -57,7 +57,8 @@
</el-row> </el-row>
</el-header> </el-header>
<ms-api-scenario-config :debug-report-id="debugReportId" @runDebug="runDebug" :is-read-only="isReadOnly" <ms-api-scenario-config :debug-report-id="debugReportId" @runDebug="runDebug" :is-read-only="isReadOnly"
:scenarios="test.scenarioDefinition" :project-id="test.projectId" ref="config"/> :test-id="test.id" :scenarios="test.scenarioDefinition" :project-id="test.projectId"
ref="config"/>
</el-container> </el-container>
</el-card> </el-card>
</ms-main-container> </ms-main-container>
@ -65,19 +66,18 @@
</template> </template>
<script> <script>
import MsApiScenarioConfig from "./components/ApiScenarioConfig"; import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import {Test} from "./model/ScenarioModel" import {Test, Scenario} from "./model/ScenarioModel"
import MsApiReportStatus from "../report/ApiReportStatus"; import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog"; import MsApiReportDialog from "./ApiReportDialog";
import {checkoutTestManagerOrTestUser, downloadFile} from "@/common/js/utils"; import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig"; import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import ApiImport from "./components/import/ApiImport"; import ApiImport from "./components/import/ApiImport";
import {getUUID} from "../../../../common/js/utils"; import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent"; import MsContainer from "@/business/components/common/components/MsContainer";
import MsContainer from "@/business/components/common/components/MsContainer"; import MsMainContainer from "@/business/components/common/components/MsMainContainer";
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
export default { export default {
name: "MsApiTestConfig", name: "MsApiTestConfig",
components: { components: {
@ -134,6 +134,39 @@
if (projectId) this.test.projectId = projectId; if (projectId) this.test.projectId = projectId;
}) })
}, },
updateReference() {
let updateIds = [];
this.test.scenarioDefinition.forEach(scenario => {
if (scenario.isReference()) {
updateIds.push(scenario.id.split("#")[0]);
}
})
if (updateIds.length === 0) return;
//
this.result = this.$post("/api/list/ids", {ids: updateIds}, response => {
let scenarioMap = {};
if (response.data) {
response.data.forEach(test => {
JSON.parse(test.scenarioDefinition).forEach(options => {
let referenceId = test.id + "#" + options.id;
scenarioMap[referenceId] = new Scenario(options);
scenarioMap[referenceId].id = referenceId;
})
})
}
let scenarios = [];
this.test.scenarioDefinition.forEach(scenario => {
if (scenario.isReference()) {
if (scenarioMap[scenario.id]) scenarios.push(scenarioMap[scenario.id]);
} else {
scenarios.push(scenario);
}
})
this.test.scenarioDefinition = scenarios;
})
},
getTest(id) { getTest(id) {
this.result = this.$get("/api/get/" + id, response => { this.result = this.$get("/api/get/" + id, response => {
if (response.data) { if (response.data) {
@ -147,6 +180,8 @@
scenarioDefinition: JSON.parse(item.scenarioDefinition), scenarioDefinition: JSON.parse(item.scenarioDefinition),
schedule: item.schedule ? item.schedule : {}, schedule: item.schedule ? item.schedule : {},
}); });
this.updateReference();
this.$refs.config.reset(); this.$refs.config.reset();
} }
}); });
@ -163,7 +198,7 @@
let jmx = this.test.toJMX(); let jmx = this.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"}); let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name); let file = new File([blob], jmx.name);
this.result = this.$fileUpload(url, file, bodyFiles, this.test, response => { this.result = this.$fileUpload(url, file, bodyFiles, this.test, () => {
if (callback) callback(); if (callback) callback();
this.create = false; this.create = false;
this.resetBodyFile(); this.resetBodyFile();
@ -339,26 +374,26 @@
created() { created() {
this.init(); this.init();
} }
} }
</script> </script>
<style scoped> <style scoped>
.test-container { .test-container {
height: calc(100vh - 155px); height: calc(100vh - 155px);
min-height: 600px; min-height: 600px;
} }
.test-name { .test-name {
width: 600px; width: 600px;
margin-left: -20px; margin-left: -20px;
margin-right: 20px; margin-right: 20px;
} }
.test-project { .test-project {
min-width: 150px; min-width: 150px;
} }
.test-container .more { .test-container .more {
margin-left: 10px; margin-left: 10px;
} }
</style> </style>

View File

@ -8,44 +8,60 @@
:title="scenario.name" :name="index" :class="{'disable-scenario': !scenario.enable}"> :title="scenario.name" :name="index" :class="{'disable-scenario': !scenario.enable}">
<template slot="title"> <template slot="title">
<div class="scenario-name"> <div class="scenario-name">
{{scenario.name}} <el-tag type="info" size="small" v-if="scenario.isReference()">{{
$t('api_test.scenario.reference')
}}
</el-tag>
{{ scenario.name }}
<span id="hint" v-if="!scenario.name"> <span id="hint" v-if="!scenario.name">
{{$t('api_test.scenario.config')}} {{ $t('api_test.scenario.config') }}
</span> </span>
</div> </div>
<el-dropdown trigger="click" @command="handleCommand"> <el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link el-icon-more scenario-btn"/> <span class="el-dropdown-link el-icon-more scenario-btn"/>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item :disabled="isReadOnly" :command="{type: 'copy', index: index}"> <el-dropdown-item :disabled="isReadOnly" :command="{type: 'copy', index: index}">
{{$t('api_test.scenario.copy')}} {{ $t('api_test.scenario.copy') }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item :disabled="isReadOnly" :command="{type:'delete', index:index}"> <el-dropdown-item :disabled="isReadOnly" :command="{type:'delete', index:index}">
{{$t('api_test.scenario.delete')}} {{ $t('api_test.scenario.delete') }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item v-if="scenario.enable" :disabled="isReadOnly" :command="{type:'disable', index:index}"> <el-dropdown-item v-if="scenario.enable" :disabled="isReadOnly"
{{$t('api_test.scenario.disable')}} :command="{type:'disable', index:index}">
{{ $t('api_test.scenario.disable') }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item v-if="!scenario.enable" :disabled="isReadOnly" :command="{type:'enable', index:index}"> <el-dropdown-item v-if="!scenario.enable" :disabled="isReadOnly"
{{$t('api_test.scenario.enable')}} :command="{type:'enable', index:index}">
{{ $t('api_test.scenario.enable') }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</template> </template>
<ms-api-request-config :is-read-only="isReadOnly" :scenario="scenario" @select="select"/> <ms-api-request-config :is-read-only="disable" :scenario="scenario" @select="select"/>
</ms-api-collapse-item> </ms-api-collapse-item>
</draggable> </draggable>
</ms-api-collapse> </ms-api-collapse>
</div> </div>
<el-button :disabled="isReadOnly" class="scenario-create" type="primary" size="mini" icon="el-icon-plus" plain @click="createScenario"/> <el-popover placement="top" v-model="visible">
<el-radio-group v-model="type" @change="createScenario">
<el-radio :label="types.CREATE">{{ $t('api_test.scenario.create_scenario') }}</el-radio>
<el-radio :label="types.SELECT">{{ $t('api_test.scenario.select_scenario') }}</el-radio>
</el-radio-group>
<el-button slot="reference" :disabled="isReadOnly" class="scenario-create" type="primary" size="mini"
icon="el-icon-plus" plain/>
</el-popover>
</el-aside> </el-aside>
<el-main class="scenario-main"> <el-main class="scenario-main">
<div class="scenario-form"> <div class="scenario-form">
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" :project-id="projectId" v-if="isScenario"/> <ms-api-scenario-form :is-read-only="disable" :scenario="selected" :project-id="projectId"
<ms-api-request-form :debug-report-id="debugReportId" @runDebug="runDebug" :is-read-only="isReadOnly" v-if="isScenario"/>
<ms-api-request-form :debug-report-id="debugReportId" @runDebug="runDebug"
:is-read-only="disable"
:request="selected" :scenario="currentScenario" v-if="isRequest"/> :request="selected" :scenario="currentScenario" v-if="isRequest"/>
</div> </div>
</el-main> </el-main>
<ms-api-scenario-select :exclude-id="testId" @select="selectScenario" ref="selectDialog"/>
</el-container> </el-container>
</template> </template>
@ -58,11 +74,13 @@ import MsApiRequestForm from "./request/ApiRequestForm";
import MsApiScenarioForm from "./ApiScenarioForm"; import MsApiScenarioForm from "./ApiScenarioForm";
import {Request, Scenario} from "../model/ScenarioModel"; import {Request, Scenario} from "../model/ScenarioModel";
import draggable from 'vuedraggable'; import draggable from 'vuedraggable';
import MsApiScenarioSelect from "@/business/components/api/test/components/ApiScenarioSelect";
export default { export default {
name: "MsApiScenarioConfig", name: "MsApiScenarioConfig",
components: { components: {
MsApiScenarioSelect,
MsApiRequestConfig, MsApiRequestConfig,
MsApiScenarioForm, MsApiScenarioForm,
MsApiRequestForm, MsApiRequestForm,
@ -72,17 +90,21 @@ export default {
}, },
props: { props: {
testId: String,
scenarios: Array, scenarios: Array,
projectId: String, projectId: String,
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false default: false
}, },
debugReportId: String debugReportId: String,
}, },
data() { data() {
return { return {
visible: false,
types: {CREATE: "create", SELECT: "select"},
type: "",
activeName: 0, activeName: 0,
selected: [Scenario, Request], selected: [Scenario, Request],
currentScenario: {} currentScenario: {}
@ -100,30 +122,49 @@ export default {
}, },
methods: { methods: {
createScenario: function () { notContainsScenario(item) {
for (let scenario of this.scenarios) {
if (item.id === scenario.id) {
return false;
}
}
return true;
},
selectScenario(selection) {
selection.filter(this.notContainsScenario).forEach(item => {
this.scenarios.push(item);
})
},
createScenario() {
if (this.type === this.types.CREATE) {
this.scenarios.push(new Scenario()); this.scenarios.push(new Scenario());
} else {
this.$refs.selectDialog.open();
}
this.visible = false;
this.type = "";
}, },
copyScenario: function (index) { copyScenario(index) {
let scenario = this.scenarios[index]; let scenario = this.scenarios[index];
this.scenarios.push(new Scenario(scenario)); this.scenarios.push(scenario.clone());
}, },
deleteScenario: function (index) { deleteScenario(index) {
this.scenarios.splice(index, 1); this.scenarios.splice(index, 1);
if (this.scenarios.length === 0) { if (this.scenarios.length === 0) {
this.createScenario(); this.createScenario();
this.select(this.scenarios[0]); this.select(this.scenarios[0]);
} }
}, },
disableScenario: function (index) { disableScenario(index) {
this.scenarios[index].enable = false; this.scenarios[index].enable = false;
}, },
enableScenario: function (index) { enableScenario(index) {
this.scenarios[index].enable = true; this.scenarios[index].enable = true;
}, },
handleChange: function (index) { handleChange(index) {
this.select(this.scenarios[index]); this.select(this.scenarios[index]);
}, },
handleCommand: function (command) { handleCommand(command) {
switch (command.type) { switch (command.type) {
case "copy": case "copy":
this.copyScenario(command.index); this.copyScenario(command.index);
@ -139,7 +180,7 @@ export default {
break; break;
} }
}, },
select: function (obj, scenario) { select(obj, scenario) {
this.selected = null; this.selected = null;
this.$nextTick(function () { this.$nextTick(function () {
if (obj instanceof Scenario) { if (obj instanceof Scenario) {
@ -150,13 +191,13 @@ export default {
this.selected = obj; this.selected = obj;
}); });
}, },
reset: function () { reset() {
this.$nextTick(function () { this.$nextTick(function () {
this.activeName = 0; this.activeName = 0;
this.select(this.scenarios[0]); this.select(this.scenarios[0]);
}); });
}, },
initScenarioEnvironment: function () { initScenarioEnvironment() {
if (this.projectId) { if (this.projectId) {
this.result = this.$get('/api/environment/list/' + this.projectId, response => { this.result = this.$get('/api/environment/list/' + this.projectId, response => {
let environments = response.data; let environments = response.data;
@ -192,62 +233,74 @@ export default {
}, },
isRequest() { isRequest() {
return this.selected instanceof Request; return this.selected instanceof Request;
},
isReference() {
if (this.selected instanceof Scenario) {
return this.selected.isReference();
} }
if (this.selected instanceof Request) {
return this.currentScenario.isReference();
}
return false;
},
disable() {
return this.isReadOnly || this.isReference;
},
}, },
created() { created() {
this.select(this.scenarios[0]); this.select(this.scenarios[0]);
} }
} }
</script> </script>
<style scoped> <style scoped>
.scenario-aside { .scenario-aside {
position: relative; position: relative;
border-radius: 4px; border-radius: 4px;
border: 1px solid #EBEEF5; border: 1px solid #EBEEF5;
box-sizing: border-box; box-sizing: border-box;
} }
.scenario-list { .scenario-list {
overflow-y: auto; overflow-y: auto;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 28px; bottom: 28px;
} }
.scenario-name { .scenario-name {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
font-size: 14px; font-size: 14px;
width: 100%; width: 100%;
} }
/*.scenario-name > #hint {*/ /*.scenario-name > #hint {*/
/*color: #8a8b8d;*/ /*color: #8a8b8d;*/
/*}*/ /*}*/
.scenario-btn { .scenario-btn {
text-align: center; text-align: center;
padding: 13px; padding: 13px;
} }
.scenario-create { .scenario-create {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
.scenario-main { .scenario-main {
position: relative; position: relative;
margin-left: 20px; margin-left: 20px;
border: 1px solid #EBEEF5; border: 1px solid #EBEEF5;
} }
.scenario-form { .scenario-form {
padding: 20px; padding: 20px;
overflow-y: auto; overflow-y: auto;
position: absolute; position: absolute;
@ -255,18 +308,18 @@ export default {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
} }
.scenario-ghost { .scenario-ghost {
opacity: 0.5; opacity: 0.5;
} }
.scenario-draggable { .scenario-draggable {
background-color: #909399; background-color: #909399;
} }
.disable-scenario >>> .el-collapse-item__header { .disable-scenario >>> .el-collapse-item__header {
border-right: 2px solid #909399; border-right: 2px solid #909399;
color: #8a8b8d; color: #8a8b8d;
} }
</style> </style>

View File

@ -1,5 +1,6 @@
<template> <template>
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px" v-loading="result.loading"> <el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px" v-loading="result.loading"
:disabled="isReadOnly">
<el-form-item :label="$t('api_test.scenario.name')" prop="name"> <el-form-item :label="$t('api_test.scenario.name')" prop="name">
<el-input :disabled="isReadOnly" v-model="scenario.name" maxlength="100" show-word-limit/> <el-input :disabled="isReadOnly" v-model="scenario.name" maxlength="100" show-word-limit/>
</el-form-item> </el-form-item>
@ -26,7 +27,7 @@
</el-form-item> </el-form-item>
</el-form-item> </el-form-item>
<el-tabs v-model="activeName"> <el-tabs v-model="activeName" :disabled="isReadOnly">
<el-tab-pane :label="$t('api_test.scenario.variables')" name="parameters"> <el-tab-pane :label="$t('api_test.scenario.variables')" name="parameters">
<ms-api-scenario-variables :is-read-only="isReadOnly" :items="scenario.variables" <ms-api-scenario-variables :is-read-only="isReadOnly" :items="scenario.variables"
:description="$t('api_test.scenario.kv_description')"/> :description="$t('api_test.scenario.kv_description')"/>
@ -38,11 +39,11 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.scenario.dubbo')" name="dubbo"> <el-tab-pane :label="$t('api_test.scenario.dubbo')" name="dubbo">
<div class="dubbo-config-title">Config Center</div> <div class="dubbo-config-title">Config Center</div>
<ms-dubbo-config-center :config="scenario.dubboConfig.configCenter"/> <ms-dubbo-config-center :config="scenario.dubboConfig.configCenter" :is-read-only="isReadOnly"/>
<div class="dubbo-config-title">Registry Center</div> <div class="dubbo-config-title">Registry Center</div>
<ms-dubbo-registry-center :registry="scenario.dubboConfig.registryCenter"/> <ms-dubbo-registry-center :registry="scenario.dubboConfig.registryCenter" :is-read-only="isReadOnly"/>
<div class="dubbo-config-title">Consumer & Service</div> <div class="dubbo-config-title">Consumer & Service</div>
<ms-dubbo-consumer-service :consumer="scenario.dubboConfig.consumerAndService"/> <ms-dubbo-consumer-service :consumer="scenario.dubboConfig.consumerAndService" :is-read-only="isReadOnly"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>

View File

@ -0,0 +1,128 @@
<template>
<el-dialog :title="$t('api_test.scenario.select_scenario')" :visible.sync="visible" width="70%">
<el-table stripe :data="tableData" size="small" @expand-change="expand">
<el-table-column type="expand" width="50">
<template v-slot:default="{row}">
<ms-api-scenario-select-sub-table :row="row" v-model="row.selected"/>
</template>
</el-table-column>
<el-table-column prop="name" :label="$t('api_test.scenario.test_name')" width="400" show-overflow-tooltip/>
<el-table-column prop="sr" :label="$t('api_test.scenario.scenario_request')" width="150" show-overflow-tooltip/>
<el-table-column prop="userName" :label="$t('api_test.creator')" width="150" show-overflow-tooltip/>
<el-table-column prop="enable" :label="$t('api_test.scenario.enable_disable')" width="150"/>
<el-table-column>
<template v-slot:header>
<div class="search-header">
<ms-table-search-bar :condition.sync="condition" @change="search" class="search-bar"
:tip="$t('commons.search_by_name')"/>
<ms-table-adv-search-bar :condition.sync="condition" @search="search"/>
</div>
</template>
<template v-slot:default="{row}">
{{ row.reference }}
<el-button type="text" size="small" @click="reference(row)">
{{ $t('api_test.scenario.reference') }}
</el-button>
<el-button type="text" size="small" @click="clone(row)">{{ $t('api_test.scenario.clone') }}</el-button>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize" :total="total"/>
<div class="dialog-footer">
<el-button @click="close">{{ $t('commons.cancel') }}</el-button>
</div>
</el-dialog>
</template>
<script>
import MsTableHeader from "@/business/components/common/components/MsTableHeader";
import {TEST_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import MsTableSearchBar from "@/business/components/common/components/MsTableSearchBar";
import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar";
import MsApiScenarioSelectSubTable from "@/business/components/api/test/components/ApiScenarioSelectSubTable";
import {Scenario} from "@/business/components/api/test/model/ScenarioModel";
export default {
name: "MsApiScenarioSelect",
components: {
MsApiScenarioSelectSubTable,
MsTableAdvSearchBar, MsTableSearchBar, MsTablePagination, MsTableHeader
},
props: {
excludeId: String
},
data() {
return {
visible: false,
condition: {
components: TEST_CONFIGS
},
tableData: [],
currentPage: 1,
pageSize: 5,
total: 0,
selection: false,
}
},
methods: {
search() {
this.condition.excludeId = this.excludeId;
let url = "/api/list/" + this.currentPage + "/" + this.pageSize;
this.result = this.$post(url, this.condition, response => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.tableData.forEach(row => {
row.selected = [];
})
});
},
reference(row) {
let scenarios = [];
for (let options of row.selected) {
if (!options.id) {
this.$warning(this.$t('api_test.scenario.cant_reference'));
return;
}
let scenario = new Scenario(options);
if (!scenario.isReference()) {
scenario.id = row.id + "#" + options.id;
} else {
scenario.id = options.id;
}
scenarios.push(scenario);
}
this.$emit('select', scenarios);
},
clone(row) {
let scenarios = [];
row.selected.forEach(options => {
scenarios.push(new Scenario(options));
})
this.$emit('select', scenarios);
},
open() {
this.search();
this.visible = true;
},
close() {
this.visible = false;
},
expand(row) {
row.selected = [];
}
}
}
</script>
<style scoped>
.search-header {
text-align: right;
}
.search-bar {
width: 200px
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<el-table stripe :data="scenarios" class="adjust-table" @select-all="select" @select="select" size="small"
:show-header="false" ref="table" v-loading="result.loading">
<el-table-column type="selection" width="50"/>
<el-table-column prop="name" width="350" show-overflow-tooltip/>
<el-table-column width="150">
<template v-slot:default="{row}">
1 / {{ row.requests.length }}
</template>
</el-table-column>
<el-table-column prop="null" width="150">
{{ row.userName }}
</el-table-column>
<el-table-column prop="enable">
<template v-slot:default="{row}">
<el-tag type="success" size="small" v-if="row.enable !== false">{{ $t('api_test.scenario.enable') }}</el-tag>
<el-tag type="info" size="small" v-else>{{ $t('api_test.scenario.disable') }}</el-tag>
</template>
</el-table-column>
<el-table-column/>
</el-table>
</template>
<script>
export default {
name: "MsApiScenarioSelectSubTable",
props: {
row: Object
},
data() {
return {
result: {loading: true},
scenarios: [],
}
},
methods: {
getTest() {
this.result = this.$get("/api/get/" + this.row.id, response => {
if (response.data) {
this.scenarios = JSON.parse(response.data.scenarioDefinition);
}
});
},
select(selection) {
this.$emit('input', selection);
}
},
mounted() {
this.getTest();
}
}
</script>
<style scoped>
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-form :model="request" :rules="rules" ref="request" label-width="100px"> <el-form :model="request" :rules="rules" ref="request" label-width="100px" :disabled="isReadOnly">
<el-form-item :label="$t('api_test.request.name')" prop="name"> <el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input :disabled="isReadOnly" v-model="request.name" maxlength="300" show-word-limit/> <el-input :disabled="isReadOnly" v-model="request.name" maxlength="300" show-word-limit/>
@ -41,7 +41,9 @@
<el-checkbox class="follow-redirects-item" v-model="request.followRedirects">{{$t('api_test.request.follow_redirects')}}</el-checkbox> <el-checkbox class="follow-redirects-item" v-model="request.followRedirects">{{$t('api_test.request.follow_redirects')}}</el-checkbox>
</el-form-item> </el-form-item>
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small" type="primary" @click="runDebug">{{ $t('api_test.request.debug') }}</el-button> <el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small"
type="primary" @click="runDebug">{{ $t('api_test.request.debug') }}
</el-button>
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.request.parameters')" name="parameters"> <el-tab-pane :label="$t('api_test.request.parameters')" name="parameters">

View File

@ -106,7 +106,6 @@ export class BaseConfig {
set(options) { set(options) {
options = this.initOptions(options) options = this.initOptions(options)
for (let name in options) { for (let name in options) {
if (options.hasOwnProperty(name)) { if (options.hasOwnProperty(name)) {
if (!(this[name] instanceof Array)) { if (!(this[name] instanceof Array)) {
@ -142,7 +141,7 @@ export class Test extends BaseConfig {
constructor(options) { constructor(options) {
super(); super();
this.type = "MS API CONFIG"; this.type = "MS API CONFIG";
this.version = '1.1.0'; this.version = '1.3.0';
this.id = uuid(); this.id = uuid();
this.name = undefined; this.name = undefined;
this.projectId = undefined; this.projectId = undefined;
@ -201,6 +200,7 @@ export class Test extends BaseConfig {
export class Scenario extends BaseConfig { export class Scenario extends BaseConfig {
constructor(options = {}) { constructor(options = {}) {
super(); super();
this.id = undefined;
this.name = undefined; this.name = undefined;
this.url = undefined; this.url = undefined;
this.variables = []; this.variables = [];
@ -216,15 +216,17 @@ export class Scenario extends BaseConfig {
this.sets({variables: KeyValue, headers: KeyValue, requests: RequestFactory}, options); this.sets({variables: KeyValue, headers: KeyValue, requests: RequestFactory}, options);
} }
initOptions(options) { initOptions(options = {}) {
options = options || {}; options.id = options.id || uuid();
options.requests = options.requests || [new RequestFactory()]; options.requests = options.requests || [new RequestFactory()];
options.dubboConfig = new DubboConfig(options.dubboConfig); options.dubboConfig = new DubboConfig(options.dubboConfig);
return options; return options;
} }
clone() { clone() {
return new Scenario(this); let clone = new Scenario(this);
clone.id = uuid();
return clone;
} }
isValid() { isValid() {
@ -238,6 +240,10 @@ export class Scenario extends BaseConfig {
} }
return {isValid: true}; return {isValid: true};
} }
isReference() {
return this.id.indexOf("#") !== -1
}
} }
class DubboConfig extends BaseConfig { class DubboConfig extends BaseConfig {
@ -296,6 +302,7 @@ export class Request extends BaseConfig {
export class HttpRequest extends Request { export class HttpRequest extends Request {
constructor(options) { constructor(options) {
super(RequestFactory.TYPES.HTTP); super(RequestFactory.TYPES.HTTP);
this.id = undefined;
this.name = undefined; this.name = undefined;
this.url = undefined; this.url = undefined;
this.path = undefined; this.path = undefined;
@ -321,8 +328,8 @@ export class HttpRequest extends Request {
this.sets({parameters: KeyValue, headers: KeyValue}, options); this.sets({parameters: KeyValue, headers: KeyValue}, options);
} }
initOptions(options) { initOptions(options = {}) {
options = options || {}; options.id = options.id || uuid();
options.method = options.method || "GET"; options.method = options.method || "GET";
options.body = new Body(options.body); options.body = new Body(options.body);
options.assertions = new Assertions(options.assertions); options.assertions = new Assertions(options.assertions);
@ -381,6 +388,7 @@ export class DubboRequest extends Request {
constructor(options = {}) { constructor(options = {}) {
super(RequestFactory.TYPES.DUBBO); super(RequestFactory.TYPES.DUBBO);
this.id = options.id || uuid();
this.name = options.name; this.name = options.name;
this.protocol = options.protocol || DubboRequest.PROTOCOLS.DUBBO; this.protocol = options.protocol || DubboRequest.PROTOCOLS.DUBBO;
this.interface = options.interface; this.interface = options.interface;

@ -1 +1 @@
Subproject commit 7e4d80cc2b870a8cac6dbb9fe6711ab6041faf6d Subproject commit 390943d21e7d0196e0d7d5faa66f0131cb631614

View File

@ -414,7 +414,15 @@ export default {
copy: "Copy scenario", copy: "Copy scenario",
delete: "Delete scenario", delete: "Delete scenario",
disable: "Disable", disable: "Disable",
enable: "Enable" enable: "Enable",
create_scenario: "Create scenario",
select_scenario: "Select scenario",
scenario_request: "Scenario/Request",
enable_disable: "Enable/Disable",
test_name: "Test Name",
reference: "Reference",
clone: "Copy",
cant_reference:'Historical test files, can be referenced after re-saving'
}, },
request: { request: {
debug: "Debug", debug: "Debug",
@ -484,7 +492,7 @@ export default {
xpath_expression: "XPath expression", xpath_expression: "XPath expression",
}, },
processor: { processor: {
pre_exec_script : "PreProcessor", pre_exec_script: "PreProcessor",
post_exec_script: "PostProcessor", post_exec_script: "PostProcessor",
code_template: "Code template", code_template: "Code template",
bean_shell_processor_tip: "Currently only BeanShell scripts are supported", bean_shell_processor_tip: "Currently only BeanShell scripts are supported",
@ -716,7 +724,7 @@ export default {
result_distribution: "Result distribution", result_distribution: "Result distribution",
custom_component: "Custom", custom_component: "Custom",
create_report: "Create report", create_report: "Create report",
defect_list:"Defect list", defect_list: "Defect list",
view_report: "View report", view_report: "View report",
component_library: "Component library", component_library: "Component library",
component_library_tip: "Drag and drop the component from the component library, add to the right, preview the report effect, only one can be added per system component.", component_library_tip: "Drag and drop the component from the component library, add to the right, preview the report effect, only one can be added per system component.",

View File

@ -414,7 +414,15 @@ export default {
copy: "复制场景", copy: "复制场景",
delete: "删除场景", delete: "删除场景",
disable: "禁用", disable: "禁用",
enable: "启用" enable: "启用",
create_scenario: "创建新场景",
select_scenario: "选择已有场景",
scenario_request: "场景/请求",
enable_disable: "启用/禁用",
test_name: "测试名称",
reference: "引用",
clone: "复制",
cant_reference: '历史测试文件,重新保存后才可被引用'
}, },
request: { request: {
debug: "调试", debug: "调试",
@ -485,7 +493,7 @@ export default {
xpath_expression: "XPath表达式", xpath_expression: "XPath表达式",
}, },
processor: { processor: {
pre_exec_script : "预执行脚本", pre_exec_script: "预执行脚本",
post_exec_script: "后执行脚本", post_exec_script: "后执行脚本",
code_template: "代码模版", code_template: "代码模版",
bean_shell_processor_tip: "仅支持 BeanShell 脚本", bean_shell_processor_tip: "仅支持 BeanShell 脚本",
@ -505,7 +513,7 @@ export default {
get_provider_success: "获取成功", get_provider_success: "获取成功",
check_registry_center: "获取失败请检查Registry Center", check_registry_center: "获取失败请检查Registry Center",
form_description: "如果当前配置项无值,则取场景配置项的值", form_description: "如果当前配置项无值,则取场景配置项的值",
} },
}, },
api_import: { api_import: {
label: "导入", label: "导入",
@ -718,7 +726,7 @@ export default {
test_result: "测试结果", test_result: "测试结果",
result_distribution: "测试结果分布", result_distribution: "测试结果分布",
custom_component: "自定义模块", custom_component: "自定义模块",
defect_list:"缺陷列表", defect_list: "缺陷列表",
create_report: "创建测试报告", create_report: "创建测试报告",
view_report: "查看测试报告", view_report: "查看测试报告",
component_library: "组件库", component_library: "组件库",

View File

@ -413,7 +413,15 @@ export default {
copy: "複製場景", copy: "複製場景",
delete: "删除場景", delete: "删除場景",
disable: "禁用", disable: "禁用",
enable: "啟用" enable: "啟用",
create_scenario: "創建新場景",
select_scenario: "選擇已有場景",
scenario_request: "場景/請求",
enable_disable: "啟用/禁用",
test_name: "測試名稱",
reference: "引用",
clone: "複製",
cant_reference: '歷史測試文件,重新保存後才可被引用'
}, },
request: { request: {
debug: "調試", debug: "調試",