This commit is contained in:
fit2-zhao 2020-12-16 17:33:42 +08:00
commit 56d8d66e92
17 changed files with 1017 additions and 471 deletions

View File

@ -1,11 +1,16 @@
package io.metersphere.api.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.ApiTestCaseResult;
import io.metersphere.api.dto.definition.SaveApiTestCaseRequest;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.base.domain.ApiTestCase;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
@ -29,6 +34,13 @@ public class ApiTestCaseController {
return apiTestCaseService.list(request);
}
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<ApiTestCase>> listSimple(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiTestCaseRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return PageUtils.setPageInfo(page, apiTestCaseService.listSimple(request));
}
@PostMapping(value = "/create", consumes = {"multipart/form-data"})
public void create(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
apiTestCaseService.create(request, bodyFiles);

View File

@ -17,5 +17,6 @@ public class ApiTestCaseRequest {
private String environmentId;
private String workspaceId;
private String apiDefinitionId;
private List<String> moduleIds;
private List<OrderRequest> orders;
}

View File

@ -1,6 +1,7 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.definition.ApiDefinitionRequest;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.ApiTestCaseResult;
import io.metersphere.api.dto.definition.SaveApiTestCaseRequest;
@ -52,6 +53,12 @@ public class ApiTestCaseService {
return extApiTestCaseMapper.list(request);
}
public List<ApiTestCase> listSimple(ApiTestCaseRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
List<ApiTestCase> apiTestCases = extApiTestCaseMapper.listSimple(request);
return apiTestCases;
}
public ApiTestCaseWithBLOBs get(String id) {
return apiTestCaseMapper.selectByPrimaryKey(id);
}

View File

@ -2,6 +2,7 @@ package io.metersphere.base.mapper.ext;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.ApiTestCaseResult;
import io.metersphere.base.domain.ApiTestCase;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -9,4 +10,5 @@ import java.util.List;
public interface ExtApiTestCaseMapper {
List<ApiTestCaseResult> list(@Param("request") ApiTestCaseRequest request);
List<ApiTestCase> listSimple(@Param("request") ApiTestCaseRequest request);
}

View File

@ -208,4 +208,19 @@
</if>
</select>
<select id="listSimple" resultType="io.metersphere.base.domain.ApiTestCase">
select
c.id, c.project_id, c.name, c.api_definition_id, c.priority, c.description, c.create_user_id, c.update_user_id, c.create_time, c.update_time,
a.moudule_id as moudule_id,
from api_test_case c
left join api_definition a
on c.api_definition_id = a.id
where c.project_id = #{request.projectId}
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
AND a.module_id in
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
#{nodeId}
</foreach>
</if>
</select>
</mapper>

View File

@ -45,6 +45,16 @@
@handleCase="handleCase"
@showExecResult="showExecResult"
ref="apiList"/>
<!--<api-case-simple-list-->
<!--v-if="item.type === 'list'"-->
<!--:current-protocol="currentProtocol"-->
<!--:visible="visible"-->
<!--:currentRow="currentRow"-->
<!--:select-node-ids="selectNodeIds"-->
<!--:trash-enable="trashEnable"-->
<!--@handleCase="handleCase"-->
<!--@showExecResult="showExecResult"-->
<!--ref="apiList"/>-->
<!-- 添加/编辑测试窗口-->
<div v-else-if="item.type=== 'ADD'" class="ms-api-div">
@ -74,7 +84,7 @@
</ms-container>
</template>
<script>
import MsApiList from './components/ApiList';
import MsApiList from './components/list/ApiList';
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsAsideContainer from "../../common/components/MsAsideContainer";
@ -90,10 +100,12 @@
import MsRunTestDubboPage from "./components/runtest/RunTestDubboPage";
import {downloadFile, getCurrentUser, getUUID, getCurrentProjectID} from "@/common/js/utils";
import MsApiModule from "./components/module/ApiModule";
import ApiCaseSimpleList from "./components/list/ApiCaseSimpleList";
export default {
name: "ApiDefinition",
components: {
ApiCaseSimpleList,
MsApiModule,
MsApiList,
MsMainContainer,
@ -249,13 +261,8 @@
this.setTabTitle(data);
this.$refs.apiList[0].initApiTable(data);
},
initTree(data) {
this.moduleOptions = data;
},
changeProtocol(data) {
this.currentProtocol = data;
},
showExecResult(row) {
showExecResult(row){
this.debug(row);
},

View File

@ -1,443 +0,0 @@
<template>
<div v-if="visible" v-loading="result.loading">
<ms-drawer :size="40" direction="bottom">
<template v-slot:header>
<api-case-header
:api="api"
@getApiTest="getApiTest"
@setEnvironment="setEnvironment"
@close="apiCaseClose"
@addCase="addCase"
@batchRun="batchRun"
:condition="condition"
:priorities="priorities"
:apiCaseList="apiCaseList"
:is-read-only="isReadOnly"
:project-id="projectId"
/>
</template>
<el-container style="padding-bottom: 200px">
<el-main v-loading="loading" style="overflow: auto">
<div v-for="(item,index) in apiCaseList" :key="index">
<el-card style="margin-top: 5px" @click.native="selectTestCase(item,$event)">
<el-row>
<el-col :span="6">
<div class="el-step__icon is-text ms-api-col">
<div class="el-step__icon-inner">{{index+1}}</div>
</div>
<label class="ms-api-label">{{$t('test_track.case.priority')}}</label>
<el-select size="small" v-model="item.priority" class="ms-api-select" @change="changePriority(item)">
<el-option v-for="grd in priorities" :key="grd.id" :label="grd.name" :value="grd.id"/>
</el-select>
</el-col>
<el-col :span="10">
<i class="icon el-icon-arrow-right" :class="{'is-active': item.active}"
@click="active(item)"/>
<el-input v-if="item.type==='create'" size="small" v-model="item.name" :name="index" :key="index"
class="ms-api-header-select" style="width: 180px"
@blur="saveTestCase(item)"/>
<span v-else>
{{item.type!= 'create' ? item.name:''}}
<i class="el-icon-edit" style="cursor:pointer" @click="showInput(item)"/>
</span>
<div v-if="item.type!='create'" style="color: #999999;font-size: 12px">
<span>
{{item.createTime | timestampFormatDate }}
{{item.createUser}} {{$t('api_test.definition.request.create_info')}}
</span>
<span>
{{item.updateTime | timestampFormatDate }}
{{item.updateUser}} {{$t('api_test.definition.request.update_info')}}
</span>
</div>
</el-col>
<el-col :span="4">
<ms-tip-button @click="singleRun(item)" :tip="$t('api_test.run')" icon="el-icon-video-play"
style="background-color: #409EFF;color: white" size="mini" :disabled="item.type=='create'" circle/>
<ms-tip-button @click="copyCase(item)" :tip="$t('commons.copy')" icon="el-icon-document-copy"
size="mini" :disabled="item.type=='create'" circle/>
<ms-tip-button @click="deleteCase(index,item)" :tip="$t('commons.delete')" icon="el-icon-delete"
size="mini" :disabled="item.type=='create'" circle/>
<ms-api-extend-btns :row="item"/>
</el-col>
<el-col :span="3">
<el-link type="danger" v-if="item.execResult && item.execResult==='error'" @click="showExecResult(item)">{{getResult(item.execResult)}}</el-link>
<el-link v-else-if="item.execResult && item.execResult==='success'" @click="showExecResult(item)">{{getResult(item.execResult)}}</el-link>
<div v-else> {{getResult(item.execResult)}}</div>
<div v-if="item.type!='create'" style="color: #999999;font-size: 12px">
<span> {{item.updateTime | timestampFormatDate }}</span>
{{item.updateUser}}
</div>
</el-col>
</el-row>
<!-- 请求参数-->
<el-collapse-transition>
<div v-if="item.active">
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<ms-api-request-form :is-read-only="isReadOnly" :headers="item.request.headers " :request="item.request" v-if="api.protocol==='HTTP'"/>
<ms-tcp-basis-parameters :request="item.request" v-if="api.protocol==='TCP'"/>
<ms-sql-basis-parameters :request="item.request" v-if="api.protocol==='SQL'"/>
<ms-dubbo-basis-parameters :request="item.request" v-if="api.protocol==='DUBBO'"/>
<!-- 保存操作 -->
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)">
{{$t('commons.save')}}
</el-button>
</div>
</el-collapse-transition>
</el-card>
</div>
</el-main>
</el-container>
</ms-drawer>
<!-- 执行组件 -->
<ms-run :debug="false" :environment="environment" :reportId="reportId" :run-data="runData"
@runRefresh="runRefresh" ref="runTest"/>
</div>
</template>
<script>
import MsTag from "../../../common/components/MsTag";
import MsTipButton from "../../../common/components/MsTipButton";
import MsApiRequestForm from "./request/http/ApiRequestForm";
import {downloadFile, getUUID, getCurrentProjectID} from "@/common/js/utils";
import ApiEnvironmentConfig from "./environment/ApiEnvironmentConfig";
import {PRIORITY, RESULT_MAP} from "../model/JsonData";
import MsApiAssertions from "./assertion/ApiAssertions";
import MsRun from "./Run";
import MsSqlBasisParameters from "./request/database/BasisParameters";
import MsTcpBasisParameters from "./request/tcp/BasisParameters";
import MsDubboBasisParameters from "./request/dubbo/BasisParameters";
import MsDrawer from "../../../common/components/MsDrawer";
import MsApiExtendBtns from "./reference/ApiExtendBtns";
import ApiCaseHeader from "./case/ApiCaseHeader";
export default {
name: 'ApiCaseList',
components: {
ApiCaseHeader,
MsDrawer,
MsTag,
MsTipButton,
MsApiRequestForm,
ApiEnvironmentConfig,
MsApiAssertions,
MsSqlBasisParameters,
MsTcpBasisParameters,
MsDubboBasisParameters,
MsRun,
MsApiExtendBtns
},
props: {
createCase: String,
loaded: Boolean,
refreshSign: String,
currentApi: {
type: Object
},
},
data() {
return {
result: {},
grades: [],
environment: {},
isReadOnly: false,
selectedEvent: Object,
priorities: PRIORITY,
apiCaseList: [],
loading: false,
runData: [],
reportId: "",
projectId: "",
checkedCases: new Set(),
visible: false,
condition: {},
api: {}
}
},
watch: {
refreshSign() {
this.api = this.currentApi;
this.getApiTest();
},
createCase() {
this.api = this.currentApi;
this.sysAddition();
}
},
created() {
this.api = this.currentApi;
this.projectId = getCurrentProjectID();
if (this.createCase) {
this.sysAddition();
} else {
this.getApiTest();
}
},
methods: {
open(api) {
this.api = api;
this.getApiTest();
this.visible = true;
},
setEnvironment(environment) {
this.environment = environment;
},
sysAddition() {
this.condition.projectId = this.projectId;
this.condition.apiDefinitionId = this.api.id;
this.$post("/api/testcase/list", this.condition, response => {
for (let index in response.data) {
let test = response.data[index];
test.request = JSON.parse(test.request);
}
this.apiCaseList = response.data;
this.addCase();
});
},
getResult(data) {
if (RESULT_MAP.get(data)) {
return RESULT_MAP.get(data);
} else {
return RESULT_MAP.get("default");
}
},
showInput(row) {
row.type = "create";
row.active = true;
this.active(row);
},
apiCaseClose() {
this.apiCaseList = [];
this.visible = false;
},
batchRun() {
if (!this.environment) {
this.$warning(this.$t('api_test.environment.select_environment'));
return;
}
if (this.apiCaseList.length > 0) {
this.apiCaseList.forEach(item => {
if (item.type != "create") {
item.request.name = item.id;
item.request.useEnvironment = this.environment.id;
this.runData.push(item.request);
}
})
if (this.runData.length > 0) {
this.loading = true;
/*触发执行操作*/
this.reportId = getUUID().substring(0, 8);
} else {
this.$warning("没有可执行的用例!");
}
} else {
this.$warning("没有可执行的用例!");
}
},
singleRun(row) {
if (!this.environment) {
this.$warning(this.$t('api_test.environment.select_environment'));
return;
}
this.runData = [];
this.loading = true;
row.request.name = row.id;
row.request.useEnvironment = this.environment.id;
this.runData.push(row.request);
/*触发执行操作*/
this.reportId = getUUID().substring(0, 8);
},
runRefresh(data) {
this.loading = false;
this.$success(this.$t('schedule.event_success'));
this.getApiTest();
this.$emit('refresh');
},
deleteCase(index, row) {
this.$get('/api/testcase/delete/' + row.id, () => {
this.$success(this.$t('commons.delete_success'));
this.apiCaseList.splice(index, 1);
this.$emit('refresh');
});
},
copyCase(data) {
let obj = {name: "copy_"+data.name, priority: data.priority, type: 'create', active: false, request: data.request};
this.apiCaseList.unshift(obj);
},
addCase() {
if (this.api.request) {
//
let request = {};
if (this.api.request instanceof Object) {
request = this.api.request;
} else {
request = JSON.parse(this.api.request);
}
let obj = {apiDefinitionId: this.api.id, name: '', priority: 'P0', type: 'create', active: false};
obj.request = request;
this.apiCaseList.unshift(obj);
}
},
active(item) {
item.active = !item.active;
},
getBodyUploadFiles(row) {
let bodyUploadFiles = [];
row.bodyUploadIds = [];
let request = row.request;
if (request.body && request.body.kvs) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
row.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
if (request.body.binary) {
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
row.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
}
}
return bodyUploadFiles;
},
getApiTest() {
if (this.api) {
this.condition.projectId = this.projectId;
this.condition.apiDefinitionId = this.api.id;
this.result = this.$post("/api/testcase/list", this.condition, response => {
for (let index in response.data) {
let test = response.data[index];
test.request = JSON.parse(test.request);
if (!test.request.hashTree) {
test.request.hashTree = [];
}
}
this.apiCaseList = response.data;
if (this.apiCaseList.length == 0) {
this.addCase();
}
});
}
},
validate(row) {
if (!row.name) {
this.$warning(this.$t('api_test.input_name'));
return true;
}
},
changePriority(row) {
if (row.type != 'create') {
this.saveTestCase(row);
}
},
saveTestCase(row) {
if (this.validate(row)) {
return;
}
let bodyFiles = this.getBodyUploadFiles(row);
row.projectId = this.projectId;
row.apiDefinitionId = row.apiDefinitionId || this.api.id;
let url = "/api/testcase/create";
if (row.id) {
url = "/api/testcase/update";
}
this.$fileUpload(url, null, bodyFiles, row, () => {
this.$success(this.$t('commons.save_success'));
this.getApiTest();
this.$emit('refresh');
});
},
selectTestCase(item, $event) {
if (item.type === "create" || !this.loaded) {
return;
}
if ($event.currentTarget.className.indexOf('is-selected') > 0) {
$event.currentTarget.className = "el-card is-always-shadow";
this.$emit('selectTestCase', null);
} else {
if (this.selectedEvent.currentTarget != undefined) {
this.selectedEvent.currentTarget.className = "el-card is-always-shadow";
}
this.selectedEvent.currentTarget = $event.currentTarget;
$event.currentTarget.className = "el-card is-always-shadow is-selected";
this.$emit('selectTestCase', item);
}
},
handleClose() {
this.visible = false;
},
showExecResult(row) {
this.visible = false;
this.$emit('showExecResult', row);
}
}
}
</script>
<style scoped>
.ms-api-select {
margin-left: 20px;
width: 80px;
}
.ms-api-header-select {
margin-left: 20px;
min-width: 100px;
}
.ms-api-label {
color: #CCCCCC;
}
.ms-api-col {
background-color: #7C3985;
border-color: #7C3985;
margin-right: 10px;
color: white;
}
.icon.is-active {
transform: rotate(90deg);
}
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
margin: 20px 0;
}
.is-selected {
background: #EFF7FF;
}
</style>

View File

@ -0,0 +1,292 @@
<template>
<el-card style="margin-top: 5px" @click.native="selectTestCase(apiCase,$event)">
<el-row>
<el-col :span="6">
<div class="el-step__icon is-text ms-api-col">
<div class="el-step__icon-inner">{{index+1}}</div>
</div>
<label class="ms-api-label">{{$t('test_track.case.priority')}}</label>
<el-select size="small" v-model="apiCase.priority" class="ms-api-select" @change="changePriority(apiCase)">
<el-option v-for="grd in priorities" :key="grd.id" :label="grd.name" :value="grd.id"/>
</el-select>
</el-col>
<el-col :span="10">
<i class="icon el-icon-arrow-right" :class="{'is-active': apiCase.active}"
@click="active(apiCase)"/>
<el-input v-if="!apiCase.id" size="small" v-model="apiCase.name" :name="index" :key="index"
class="ms-api-header-select" style="width: 180px"
@blur="saveTestCase(apiCase)"/>
<span v-else>
{{apiCase.id ? apiCase.name:''}}
<i class="el-icon-edit" style="cursor:pointer" @click="showInput(apiCase)"/>
</span>
<div v-if="apiCase.id" style="color: #999999;font-size: 12px">
<span>
{{apiCase.createTime | timestampFormatDate }}
{{apiCase.createUser}} {{$t('api_test.definition.request.create_info')}}
</span>
<span>
{{apiCase.updateTime | timestampFormatDate }}
{{apiCase.updateUser}} {{$t('api_test.definition.request.update_info')}}
</span>
</div>
</el-col>
<el-col :span="4">
<ms-tip-button @click="singleRun(apiCase)" :tip="$t('api_test.run')" icon="el-icon-video-play"
style="background-color: #409EFF;color: white" size="mini" :disabled="!apiCase.id" circle/>
<ms-tip-button @click="copyCase(apiCase)" :tip="$t('commons.copy')" icon="el-icon-document-copy"
size="mini" :disabled="!apiCase.id" circle/>
<ms-tip-button @click="deleteCase(index,apiCase)" :tip="$t('commons.delete')" icon="el-icon-delete"
size="mini" :disabled="!apiCase.id" circle/>
<ms-api-extend-btns :row="apiCase"/>
</el-col>
<el-col :span="3">
<el-link type="danger" v-if="apiCase.execResult && apiCase.execResult==='error'" @click="showExecResult(apiCase)">{{getResult(apiCase.execResult)}}</el-link>
<el-link v-else-if="apiCase.execResult && apiCase.execResult==='success'" @click="showExecResult(apiCase)">{{getResult(apiCase.execResult)}}</el-link>
<div v-else> {{getResult(apiCase.execResult)}}</div>
<div v-if="apiCase.id" style="color: #999999;font-size: 12px">
<span> {{apiCase.updateTime | timestampFormatDate }}</span>
{{apiCase.updateUser}}
</div>
</el-col>
</el-row>
<!-- 请求参数-->
<el-collapse-transition>
<div v-if="apiCase.active">
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<ms-api-request-form :is-read-only="isReadOnly" :headers="apiCase.request.headers " :request="apiCase.request" v-if="api.protocol==='HTTP'"/>
<ms-tcp-basis-parameters :request="apiCase.request" v-if="api.protocol==='TCP'"/>
<ms-sql-basis-parameters :request="apiCase.request" v-if="api.protocol==='SQL'"/>
<ms-dubbo-basis-parameters :request="apiCase.request" v-if="api.protocol==='DUBBO'"/>
<!-- 保存操作 -->
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(apiCase)">
{{$t('commons.save')}}
</el-button>
</div>
</el-collapse-transition>
</el-card>
</template>
<script>
import {getCurrentProjectID, getUUID} from "../../../../../../common/js/utils";
import {PRIORITY, RESULT_MAP} from "../../model/JsonData";
import MsTag from "../../../../common/components/MsTag";
import MsTipButton from "../../../../common/components/MsTipButton";
import MsApiRequestForm from "../request/http/ApiRequestForm";
import ApiEnvironmentConfig from "../environment/ApiEnvironmentConfig";
import MsApiAssertions from "../assertion/ApiAssertions";
import MsSqlBasisParameters from "../request/database/BasisParameters";
import MsTcpBasisParameters from "../request/tcp/BasisParameters";
import MsDubboBasisParameters from "../request/dubbo/BasisParameters";
import MsApiExtendBtns from "../reference/ApiExtendBtns";
export default {
name: "ApiCaseItem",
components: {
MsTag,
MsTipButton,
MsApiRequestForm,
ApiEnvironmentConfig,
MsApiAssertions,
MsSqlBasisParameters,
MsTcpBasisParameters,
MsDubboBasisParameters,
MsApiExtendBtns
},
data() {
return {
result: {},
grades: [],
environment: {},
isReadOnly: false,
selectedEvent: Object,
priorities: PRIORITY,
runData: [],
reportId: "",
checkedCases: new Set(),
visible: false,
condition: {},
}
},
props: {
apiCase: {
type: Object,
default() {
return {}
}
},
index: {
type: Number,
default() {
return 0
}
},
api: {
type: Object,
default() {
return {}
}
}
},
watch: {
},
methods: {
deleteCase(index, row) {
this.$get('/api/testcase/delete/' + row.id, () => {
this.$success(this.$t('commons.delete_success'));
this.$emit('refresh');
});
},
singleRun(data) {
this.$emit('singleRun', data);
},
copyCase(data) {
let obj = {name: "copy_" + data.name, priority: data.priority, active: false, request: data.request};
this.apiCaseList.unshift(obj);
},
selectTestCase(item, $event) {
if (!item.id || !this.loaded) {
return;
}
if ($event.currentTarget.className.indexOf('is-selected') > 0) {
$event.currentTarget.className = "el-card is-always-shadow";
this.$emit('selectTestCase', null);
} else {
if (this.selectedEvent.currentTarget != undefined) {
this.selectedEvent.currentTarget.className = "el-card is-always-shadow";
}
this.selectedEvent.currentTarget = $event.currentTarget;
$event.currentTarget.className = "el-card is-always-shadow is-selected";
this.$emit('selectTestCase', item);
}
},
changePriority(row) {
if (row.id) {
this.saveTestCase(row);
}
},
saveTestCase(row) {
if (this.validate(row)) {
return;
}
let bodyFiles = this.getBodyUploadFiles(row);
row.projectId = getCurrentProjectID();
row.apiDefinitionId = row.apiDefinitionId || this.api.id;
let url = "/api/testcase/create";
if (row.id) {
url = "/api/testcase/update";
}
this.$fileUpload(url, null, bodyFiles, row, () => {
this.$success(this.$t('commons.save_success'));
this.$emit('refresh');
});
},
showInput(row) {
// row.type = "create";
row.active = true;
this.active(row);
},
active(item) {
item.active = !item.active;
},
getResult(data) {
if (RESULT_MAP.get(data)) {
return RESULT_MAP.get(data);
} else {
return RESULT_MAP.get("default");
}
},
validate(row) {
if (!row.name) {
this.$warning(this.$t('api_test.input_name'));
return true;
}
},
getBodyUploadFiles(row) {
let bodyUploadFiles = [];
row.bodyUploadIds = [];
let request = row.request;
if (request.body && request.body.kvs) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
row.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
if (request.body.binary) {
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
row.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
}
}
return bodyUploadFiles;
},
}
}
</script>
<style scoped>
.ms-api-select {
margin-left: 20px;
width: 80px;
}
.ms-api-header-select {
margin-left: 20px;
min-width: 100px;
}
.ms-api-label {
color: #CCCCCC;
}
.ms-api-col {
background-color: #7C3985;
border-color: #7C3985;
margin-right: 10px;
color: white;
}
.icon.is-active {
transform: rotate(90deg);
}
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
margin: 20px 0;
}
.is-selected {
background: #EFF7FF;
}
</style>

View File

@ -0,0 +1,232 @@
<template>
<div v-if="visible">
<ms-drawer :size="40" direction="bottom">
<template v-slot:header>
<api-case-header
:api="api"
@getApiTest="getApiTest"
@setEnvironment="setEnvironment"
@close="apiCaseClose"
@addCase="addCase"
@batchRun="batchRun"
:condition="condition"
:priorities="priorities"
:apiCaseList="apiCaseList"
:is-read-only="isReadOnly"
:project-id="projectId"
/>
</template>
<el-container v-loading="result.loading" style="padding-bottom: 200px">
<el-main v-loading="batchLoading" style="overflow: auto">
<div v-for="(item,index) in apiCaseList" :key="index">
<api-case-item v-loading="singleLoading && singleRunId === item.id"
@refresh="getApiTest"
@singleRun="singleRun"
:api="api"
:api-case="item" :index="index"/>
</div>
</el-main>
</el-container>
</ms-drawer>
<!-- 执行组件 -->
<ms-run :debug="false" :environment="environment" :reportId="reportId" :run-data="runData"
@runRefresh="runRefresh" ref="runTest"/>
</div>
</template>
<script>
import ApiCaseHeader from "./ApiCaseHeader";
import ApiCaseItem from "./ApiCaseItem";
import MsRun from "../Run";
import {downloadFile, getUUID, getCurrentProjectID} from "@/common/js/utils";
import MsDrawer from "../../../../common/components/MsDrawer";
import {PRIORITY} from "../../model/JsonData";
export default {
name: 'ApiCaseList',
components: {
MsDrawer,
MsRun,
ApiCaseHeader,
ApiCaseItem,
},
props: {
createCase: String,
loaded: Boolean,
refreshSign: String,
currentApi: {
type: Object
},
},
data() {
return {
result: {},
grades: [],
environment: {},
isReadOnly: false,
selectedEvent: Object,
priorities: PRIORITY,
apiCaseList: [],
batchLoading: false,
singleLoading: false,
singleRunId: "",
runData: [],
reportId: "",
projectId: "",
checkedCases: new Set(),
visible: false,
condition: {},
api: {}
}
},
watch: {
refreshSign() {
this.api = this.currentApi;
this.getApiTest();
},
createCase() {
this.api = this.currentApi;
this.sysAddition();
}
},
created() {
this.api = this.currentApi;
this.projectId = getCurrentProjectID();
if (this.createCase) {
this.sysAddition();
} else {
this.getApiTest();
}
},
methods: {
open(api) {
this.api = api;
this.getApiTest();
this.visible = true;
},
setEnvironment(environment) {
this.environment = environment;
},
sysAddition() {
this.condition.projectId = this.projectId;
this.condition.apiDefinitionId = this.api.id;
this.$post("/api/testcase/list", this.condition, response => {
for (let index in response.data) {
let test = response.data[index];
test.request = JSON.parse(test.request);
}
this.apiCaseList = response.data;
this.addCase();
});
},
apiCaseClose() {
this.apiCaseList = [];
this.visible = false;
},
runRefresh(data) {
this.batchLoading = false;
this.singleLoading = false;
this.singleRunId = "";
this.$success(this.$t('schedule.event_success'));
this.getApiTest();
this.$emit('refresh');
},
getApiTest() {
if (this.api) {
this.condition.projectId = this.projectId;
this.condition.apiDefinitionId = this.api.id;
this.result = this.$post("/api/testcase/list", this.condition, response => {
for (let index in response.data) {
let test = response.data[index];
test.request = JSON.parse(test.request);
if (!test.request.hashTree) {
test.request.hashTree = [];
}
}
this.apiCaseList = response.data;
if (this.apiCaseList.length == 0) {
this.addCase();
}
});
}
},
addCase() {
if (this.api.request) {
//
let request = {};
if (this.api.request instanceof Object) {
request = this.api.request;
} else {
request = JSON.parse(this.api.request);
}
let obj = {apiDefinitionId: this.api.id, name: '', priority: 'P0', active: false};
obj.request = request;
this.apiCaseList.unshift(obj);
}
},
handleClose() {
this.visible = false;
},
showExecResult(row) {
this.visible = false;
this.$emit('showExecResult', row);
},
singleRun(row) {
if (!this.environment) {
this.$warning(this.$t('api_test.environment.select_environment'));
return;
}
this.runData = [];
this.singleLoading = true;
this.singleRunId = row.id;
row.request.name = row.id;
row.request.useEnvironment = this.environment.id;
this.runData.push(row.request);
/*触发执行操作*/
this.reportId = getUUID().substring(0, 8);
},
batchRun() {
if (!this.environment) {
this.$warning(this.$t('api_test.environment.select_environment'));
return;
}
if (this.apiCaseList.length > 0) {
this.apiCaseList.forEach(item => {
if (item.id) {
item.request.name = item.id;
item.request.useEnvironment = this.environment.id;
this.runData.push(item.request);
}
})
if (this.runData.length > 0) {
this.batchLoading = true;
/*触发执行操作*/
this.reportId = getUUID().substring(0, 8);
} else {
this.$warning("没有可执行的用例!");
}
} else {
this.$warning("没有可执行的用例!");
}
},
refresh() {
this.getApiTest();
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,378 @@
<template>
<div>
<api-list-container>
<el-input placeholder="搜索" @blur="search" class="search-input" size="small" v-model="condition.name"/>
<el-table v-loading="result.loading"
border
:data="tableData" row-key="id" class="test-content adjust-table"
@select-all="handleSelectAll"
@select="handleSelect" :height="screenHeight">
<el-table-column type="selection"/>
<el-table-column width="40" :resizable="false" align="center">
<template v-slot:default="scope">
<show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectRows.size"/>
</template>
</el-table-column>
<el-table-column prop="name" :label="$t('api_test.definition.api_name')" show-overflow-tooltip/>
<el-table-column
prop="priority"
:filters="priorityFilters"
column-key="priority"
:label="$t('test_track.case.priority')"
show-overflow-tooltip>
<template v-slot:default="scope">
<priority-table-item :value="scope.row.priority"/>
</template>
</el-table-column>
<el-table-column
prop="path"
:label="$t('api_test.definition.api_path')"
show-overflow-tooltip/>
<el-table-column
prop="createUserId"
:label="'创建人'"
show-overflow-tooltip/>
<el-table-column width="160" :label="$t('api_test.definition.api_last_time')" prop="updateTime">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" min-width="130" align="center">
<template v-slot:default="scope">
<el-button type="text" @click="reductionApi(scope.row)" v-if="trashEnable">恢复</el-button>
<el-button type="text" @click="editCase(scope.row)" v-else>{{$t('commons.edit')}}</el-button>
<el-button type="text" @click="handleDelete(scope.row)" style="color: #F56C6C">{{$t('commons.delete')}}</el-button>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="initApiTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</api-list-container>
<api-case-list @refresh="initApiTable" @showExecResult="showExecResult" :currentApi="selectApi" ref="caseList"/>
<!--批量编辑-->
<ms-batch-edit ref="batchEdit" @batchEdit="batchEdit" :typeArr="typeArr" :value-arr="valueArr"/>
</div>
</template>
<script>
import MsTableHeader from '../../../../common/components/MsTableHeader';
import MsTableOperator from "../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
import MsTableButton from "../../../../common/components/MsTableButton";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import MsTablePagination from "../../../../common/pagination/TablePagination";
import MsTag from "../../../../common/components/MsTag";
import MsApiCaseList from "../case/ApiCaseList";
import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../basis/BatchEdit";
import {API_METHOD_COLOUR, REQ_METHOD, API_STATUS} from "../../model/JsonData";
import {getCurrentProjectID} from "@/common/js/utils";
import ApiListContainer from "./ApiListContainer";
import PriorityTableItem from "../../../../track/common/tableItems/planview/PriorityTableItem";
import ApiCaseList from "../case/ApiCaseList";
export default {
name: "ApiCaseSimpleList",
components: {
ApiCaseList,
PriorityTableItem,
ApiListContainer,
MsTableButton,
MsTableOperatorButton,
MsTableOperator,
MsTableHeader,
MsTablePagination,
MsTag,
MsApiCaseList,
MsContainer,
MsBottomContainer,
ShowMoreBtn,
MsBatchEdit
},
data() {
return {
condition: {},
selectApi: {},
result: {},
moduleId: "",
deletePath: "/test/case/delete",
selectRows: new Set(),
buttons: [
{name: this.$t('api_test.definition.request.batch_delete'), handleClick: this.handleDeleteBatch},
{name: this.$t('api_test.definition.request.batch_edit'), handleClick: this.handleEditBatch}
],
typeArr: [
{id: 'status', name: this.$t('api_test.definition.api_case_status')},
{id: 'method', name: this.$t('api_test.definition.api_type')},
{id: 'userId', name: this.$t('api_test.definition.api_principal')},
],
valueArr: {
status: API_STATUS,
method: REQ_METHOD,
userId: [],
},
methodColorMap: new Map(API_METHOD_COLOUR),
tableData: [],
currentPage: 1,
pageSize: 10,
total: 0,
projectId: "",
screenHeight: document.documentElement.clientHeight - 330,//
}
},
props: {
currentProtocol: String,
selectNodeIds: Array,
visible: {
type: Boolean,
default: false,
},
trashEnable: {
type: Boolean,
default: false,
}
},
created: function () {
this.projectId = getCurrentProjectID();
this.initApiTable();
this.getMaintainerOptions();
},
watch: {
selectNodeIds() {
this.initApiTable();
},
currentProtocol() {
this.initApiTable();
},
trashEnable() {
if (this.trashEnable) {
this.initApiTable();
}
},
},
methods: {
initApiTable() {
this.selectRows = new Set();
this.condition.filters = ["Prepare", "Underway", "Completed"];
this.condition.moduleIds = this.selectNodeIds;
if (this.trashEnable) {
this.condition.filters = ["Trash"];
this.condition.moduleIds = [];
}
if (this.projectId != null) {
this.condition.projectId = this.projectId;
}
if (this.currentProtocol != null) {
this.condition.protocol = this.currentProtocol;
}
this.result = this.$post("/api/testcase/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => {
this.total = response.data.itemCount;
this.tableData = response.data.listObject;
});
},
// getMaintainerOptions() {
// let workspaceId = localStorage.getItem(WORKSPACE_ID);
// this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
// this.valueArr.userId = response.data;
// });
// },
handleSelect(selection, row) {
row.hashTree = [];
if (this.selectRows.has(row)) {
this.$set(row, "showMore", false);
this.selectRows.delete(row);
} else {
this.$set(row, "showMore", true);
this.selectRows.add(row);
}
let arr = Array.from(this.selectRows);
// 1
if (this.selectRows.size === 1) {
this.$set(arr[0], "showMore", false);
} else if (this.selectRows.size === 2) {
arr.forEach(row => {
this.$set(row, "showMore", true);
})
}
},
handleSelectAll(selection) {
if (selection.length > 0) {
if (selection.length === 1) {
selection.hashTree = [];
this.selectRows.add(selection[0]);
} else {
this.tableData.forEach(item => {
item.hashTree = [];
this.$set(item, "showMore", true);
this.selectRows.add(item);
});
}
} else {
this.selectRows.clear();
this.tableData.forEach(row => {
this.$set(row, "showMore", false);
})
}
},
search() {
this.initApiTable();
},
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
// handleTestCase(api) {
// this.selectApi = api;
// let request = {};
// if (Object.prototype.toString.call(api.request).match(/\[object (\w+)\]/)[1].toLowerCase() === 'object') {
// request = api.request;
// } else {
// request = JSON.parse(api.request);
// }
// if (!request.hashTree) {
// request.hashTree = [];
// }
// this.selectApi.url = request.path;
// this.$refs.caseList.open(this.selectApi);
// },
editCase(row) {
// this.$emit('editCase', row);
this.$get('/api/definition/' + row.api_definition_id, (response) => {
})
// this.selectApi = api;
// let request = {};
// if (Object.prototype.toString.call(api.request).match(/\[object (\w+)\]/)[1].toLowerCase() === 'object') {
// request = api.request;
// } else {
// request = JSON.parse(api.request);
// }
// if (!request.hashTree) {
// request.hashTree = [];
// }
// this.selectApi.url = request.path;
// this.$refs.caseList.open(this.selectApi);
},
reductionApi(row) {
let ids = [row.id];
this.$post('/api/definition/reduction/', ids, () => {
this.$success(this.$t('commons.save_success'));
this.search();
});
},
handleDeleteBatch() {
if (this.trashEnable) {
this.$alert(this.$t('api_test.definition.request.delete_confirm') + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = Array.from(this.selectRows).map(row => row.id);
this.$post('/api/definition/deleteBatch/', ids, () => {
this.selectRows.clear();
this.initApiTable();
this.$success(this.$t('commons.delete_success'));
});
}
}
});
} else {
this.$alert(this.$t('api_test.definition.request.delete_confirm') + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = Array.from(this.selectRows).map(row => row.id);
this.$post('/api/definition/removeToGc/', ids, () => {
this.selectRows.clear();
this.initApiTable();
this.$success(this.$t('commons.delete_success'));
});
}
}
});
}
},
handleEditBatch() {
this.$refs.batchEdit.open();
},
batchEdit(form) {
let arr = Array.from(this.selectRows);
let ids = arr.map(row => row.id);
let param = {};
param[form.type] = form.value;
param.ids = ids;
this.$post('/api/definition/batch/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.initApiTable();
});
},
handleDelete(api) {
if (this.trashEnable) {
this.$get('/api/definition/delete/' + api.id, () => {
this.$success(this.$t('commons.delete_success'));
this.initApiTable();
});
return;
}
this.$alert(this.$t('api_test.definition.request.delete_confirm') + ' ' + api.name + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = [api.id];
this.$post('/api/definition/removeToGc/', ids, () => {
this.$success(this.$t('commons.delete_success'));
this.initApiTable();
});
}
}
});
},
getColor(enable, method) {
if (enable) {
return this.methodColorMap.get(method);
}
},
showExecResult(row) {
this.$emit('showExecResult', row);
}
},
}
</script>
<style scoped>
.operate-button > div {
display: inline-block;
margin-left: 10px;
}
.request-method {
padding: 0 5px;
color: #1E90FF;
}
.api-el-tag {
color: white;
}
.search-input {
float: right;
width: 300px;
/*margin-bottom: 20px;*/
margin-right: 20px;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div>
<el-card class="card-content">
<api-list-container>
<el-input placeholder="搜索" @blur="search" class="search-input" size="small" v-model="condition.name"/>
<el-table v-loading="result.loading"
@ -82,7 +82,7 @@
</el-table>
<ms-table-pagination :change="initApiTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
</api-list-container>
<ms-api-case-list @refresh="initApiTable" @showExecResult="showExecResult" :currentApi="selectApi" ref="caseList"/>
<!--批量编辑-->
<ms-batch-edit ref="batchEdit" @batchEdit="batchEdit" :typeArr="typeArr" :value-arr="valueArr"/>
@ -92,25 +92,27 @@
<script>
import MsTableHeader from '../../../../components/common/components/MsTableHeader';
import MsTableOperator from "../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
import MsTableButton from "../../../common/components/MsTableButton";
import MsTableHeader from '../../../../common/components/MsTableHeader';
import MsTableOperator from "../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
import MsTableButton from "../../../../common/components/MsTableButton";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import MsTablePagination from "../../../common/pagination/TablePagination";
import MsTag from "../../../common/components/MsTag";
import MsApiCaseList from "./ApiCaseList";
import MsContainer from "../../../common/components/MsContainer";
import MsBottomContainer from "./BottomContainer";
import ShowMoreBtn from "../../../../components/track/case/components/ShowMoreBtn";
import MsBatchEdit from "./basis/BatchEdit";
import {API_METHOD_COLOUR, REQ_METHOD, API_STATUS} from "../model/JsonData";
import MsTablePagination from "../../../../common/pagination/TablePagination";
import MsTag from "../../../../common/components/MsTag";
import MsApiCaseList from "../case/ApiCaseList";
import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../basis/BatchEdit";
import {API_METHOD_COLOUR, REQ_METHOD, API_STATUS} from "../../model/JsonData";
import {getCurrentProjectID} from "@/common/js/utils";
import {WORKSPACE_ID} from '../../../../../common/js/constants';
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import ApiListContainer from "./ApiListContainer";
export default {
name: "ApiList",
components: {
ApiListContainer,
MsTableButton,
MsTableOperatorButton,
MsTableOperator,

View File

@ -0,0 +1,41 @@
<template>
<el-card class="card-content" v-if="isShow">
<el-button-group>
<el-button plain size="small" icon="el-icon-tickets" :class="{active: activeButton == 'api'}" @click="click('api')"></el-button>
<el-button plain size="small" icon="el-icon-paperclip" :class="{active: activeButton == 'case'}" @click="click('case')"></el-button>
</el-button-group>
<slot></slot>
</el-card>
</template>
<script>
export default {
name: "ApiListContainer",
data() {
return {
activeButton: 'api',
isShow: true
}
},
methods: {
click(type) {
this.activeButton = type;
// this.reload();
},
// reload() {
// this.isShow = false;
// this.$nextTick(() => {
// this.isShow = true;
// })
// }
}
}
</script>
<style scoped>
/*.active {*/
/*background-color: #409EFF;*/
/*}*/
</style>

View File

@ -45,7 +45,7 @@
<script>
import MsApiRequestForm from "../request/http/ApiRequestForm";
import {downloadFile, getUUID, getCurrentProjectID} from "@/common/js/utils";
import MsApiCaseList from "../ApiCaseList";
import MsApiCaseList from "../case/ApiCaseList";
import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer";
import {parseEnvironment} from "../../model/EnvironmentModel";

View File

@ -89,7 +89,7 @@
<script>
import MsApiRequestForm from "../request/http/ApiRequestForm";
import {downloadFile, getUUID, getCurrentProjectID} from "@/common/js/utils";
import MsApiCaseList from "../ApiCaseList";
import MsApiCaseList from "../case/ApiCaseList";
import MsContainer from "../../../../common/components/MsContainer";
import {parseEnvironment} from "../../model/EnvironmentModel";
import ApiEnvironmentConfig from "../environment/ApiEnvironmentConfig";

View File

@ -44,7 +44,7 @@
<script>
import MsApiRequestForm from "../request/http/ApiRequestForm";
import {downloadFile, getUUID, getCurrentProjectID} from "@/common/js/utils";
import MsApiCaseList from "../ApiCaseList";
import MsApiCaseList from "../case/ApiCaseList";
import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer";
import {parseEnvironment} from "../../model/EnvironmentModel";

View File

@ -44,7 +44,7 @@
<script>
import MsApiRequestForm from "../request/http/ApiRequestForm";
import {downloadFile, getUUID, getCurrentProjectID} from "@/common/js/utils";
import MsApiCaseList from "../ApiCaseList";
import MsApiCaseList from "../case/ApiCaseList";
import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer";
import {parseEnvironment} from "../../model/EnvironmentModel";

@ -1 +1 @@
Subproject commit e1e8b4dffb4f71f1402ddae6bb149dd0be195342
Subproject commit d39dafaf84b9c7a56cb51f2caf67dd7dfde5938c