This commit is contained in:
fit2-zhao 2020-12-24 12:17:45 +08:00
commit caa36951cc
15 changed files with 781 additions and 67 deletions

View File

@ -268,7 +268,7 @@ public class APITestController {
if(allCount!=0){ if(allCount!=0){
float coverageRageNumber =(float)apiCountResult.getExecutionPassCount()*100/allCount; float coverageRageNumber =(float)apiCountResult.getExecutionPassCount()*100/allCount;
DecimalFormat df = new DecimalFormat("0.0"); DecimalFormat df = new DecimalFormat("0.0");
apiCountResult.setCoverageRage(df.format(coverageRageNumber)+"%"); apiCountResult.setPassRage(df.format(coverageRageNumber)+"%");
} }
return apiCountResult; return apiCountResult;

View File

@ -21,6 +21,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Map;
@RestController @RestController
@RequestMapping(value = "/api/testcase") @RequestMapping(value = "/api/testcase")
@ -43,6 +44,12 @@ public class ApiTestCaseController {
return PageUtils.setPageInfo(page, apiTestCaseService.listSimple(request)); return PageUtils.setPageInfo(page, apiTestCaseService.listSimple(request));
} }
@PostMapping("/get/request")
public Map<String, String> listSimple(@RequestBody ApiTestCaseRequest request) {
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return apiTestCaseService.getRequest(request);
}
@PostMapping(value = "/create", consumes = {"multipart/form-data"}) @PostMapping(value = "/create", consumes = {"multipart/form-data"})
public void create(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public void create(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
apiTestCaseService.create(request, bodyFiles); apiTestCaseService.create(request, bodyFiles);

View File

@ -328,4 +328,9 @@ public class ApiTestCaseService {
example.createCriteria().andIdIn(caseIds); example.createCriteria().andIdIn(caseIds);
return apiTestCaseMapper.selectByExample(example); return apiTestCaseMapper.selectByExample(example);
} }
public Map<String, String> getRequest(ApiTestCaseRequest request) {
List<ApiTestCaseWithBLOBs> list = extApiTestCaseMapper.getRequest(request);
return list.stream().collect(Collectors.toMap(ApiTestCaseWithBLOBs::getId, ApiTestCaseWithBLOBs::getRequest));
}
} }

View File

@ -50,12 +50,20 @@
) caseErrorCountData ON caseErrorCountData.testCaseID =testCase.testCaseID ) caseErrorCountData ON caseErrorCountData.testCaseID =testCase.testCaseID
WHERE caseErrorCountData.executeTime >= #{startTimestamp} WHERE caseErrorCountData.executeTime >= #{startTimestamp}
UNION UNION
SELECT scene.`name` AS caseName,testPlan.`name` AS testPlan,count(report.id) AS failureTimes,'scenario' AS caseType SELECT scene.`name` AS caseName,apiScene.testPlanName AS testPlan,count(report.id) AS failureTimes,'scenario' AS caseType
FROM api_scenario_report report FROM api_scenario_report report
INNER JOIN api_scenario_report_detail reportDetail ON report.id = reportDetail.report_id INNER JOIN api_scenario_report_detail reportDetail ON report.id = reportDetail.report_id
INNER JOIN api_scenario scene ON reportDetail.content like concat('%"', scene.`name`,'"%') INNER JOIN api_scenario scene ON reportDetail.content like concat('%"', scene.`name`,'"%')
LEFT JOIN test_plan_api_scenario apiScene ON apiScene.api_scenario_id = scene.id LEFT JOIN
LEFT JOIN test_plan testPlan ON testPlan.id = apiScene.test_plan_id (
SELECT
apiScene.api_scenario_id,
group_concat(testPlan.`name`) AS testPlanName
FROM
test_plan_api_scenario apiScene
INNER JOIN test_plan testPlan ON testPlan.id = apiScene.test_plan_id
GROUP BY apiScene.api_scenario_id
)apiScene ON apiScene.api_scenario_id = scene.id
WHERE report.project_id = #{projectId} WHERE report.project_id = #{projectId}
AND report.status = 'Error' AND report.create_time >= #{startTimestamp} AND report.status = 'Error' AND report.create_time >= #{startTimestamp}
GROUP BY scene.id GROUP BY scene.id

View File

@ -4,10 +4,13 @@ import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.api.dto.definition.ApiTestCaseDTO; import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCaseRequest; import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.ApiTestCaseResult; import io.metersphere.api.dto.definition.ApiTestCaseResult;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import java.util.List; import java.util.List;
import java.util.Map;
public interface ExtApiTestCaseMapper { public interface ExtApiTestCaseMapper {
@ -20,4 +23,6 @@ public interface ExtApiTestCaseMapper {
List<ApiDataCountResult> countProtocolByProjectID(String projectId); List<ApiDataCountResult> countProtocolByProjectID(String projectId);
long countByProjectIDAndCreateInThisWeek(@Param("projectId") String projectId, @Param("firstDayTimestamp") long firstDayTimestamp, @Param("lastDayTimestamp") long lastDayTimestamp); long countByProjectIDAndCreateInThisWeek(@Param("projectId") String projectId, @Param("firstDayTimestamp") long firstDayTimestamp, @Param("lastDayTimestamp") long lastDayTimestamp);
List<ApiTestCaseWithBLOBs> getRequest(@Param("request") ApiTestCaseRequest request);
} }

View File

@ -290,5 +290,20 @@
AND testCase.create_time BETWEEN #{firstDayTimestamp} AND #{lastDayTimestamp} AND testCase.create_time BETWEEN #{firstDayTimestamp} AND #{lastDayTimestamp}
</select> </select>
<select id="getRequest" resultType="io.metersphere.base.domain.ApiTestCaseWithBLOBs">
select id, request
from api_test_case
where 1
<if test="request.id != null and request.id!=''">
and id = #{request.id}
</if>
<if test="request.ids != null and request.ids.size() > 0">
and id in
<foreach collection="request.ids" item="caseId" separator="," open="(" close=")">
#{caseId}
</foreach>
</if>
</select>
</mapper> </mapper>

View File

@ -204,6 +204,7 @@
<select id="selectTestPlanByRelevancy" resultMap="BaseResultMap" parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest"> <select id="selectTestPlanByRelevancy" resultMap="BaseResultMap" parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
SELECT * FROM TEST_PLAN p LEFT JOIN test_plan_project t ON t.test_plan_id=p.id SELECT * FROM TEST_PLAN p LEFT JOIN test_plan_project t ON t.test_plan_id=p.id
<where> <where>
AND t.project_id = #{request.projectId}
<if test="request.scenarioId != null"> <if test="request.scenarioId != null">
AND p.id IN (SELECT test_plan_id FROM test_plan_api_scenario WHERE api_scenario_id = #{request.scenarioId} ) AND p.id IN (SELECT test_plan_id FROM test_plan_api_scenario WHERE api_scenario_id = #{request.scenarioId} )
</if> </if>

View File

@ -212,6 +212,8 @@ public class SystemParameterService {
public void saveBaseInfo(List<SystemParameter> parameters) { public void saveBaseInfo(List<SystemParameter> parameters) {
SystemParameterExample example = new SystemParameterExample(); SystemParameterExample example = new SystemParameterExample();
parameters.forEach(param -> { parameters.forEach(param -> {
// 去掉路径最后的 /
param.setParamValue(StringUtils.removeEnd(param.getParamValue(), "/"));
example.createCriteria().andParamKeyEqualTo(param.getParamKey()); example.createCriteria().andParamKeyEqualTo(param.getParamKey());
if (systemParameterMapper.countByExample(example) > 0) { if (systemParameterMapper.countByExample(example) > 0) {
systemParameterMapper.updateByPrimaryKey(param); systemParameterMapper.updateByPrimaryKey(param);

@ -1 +1 @@
Subproject commit 79343a2763b014355f91fc21b2356a95ae437973 Subproject commit 9f4a9bbf46fc1333dbcccea21f83e27e3ec10b1f

View File

@ -1,6 +1,6 @@
<template> <template>
<el-card class="card-content"> <el-card>
<div> <div class="card-content">
<div class="ms-main-div" @click="showAll"> <div class="ms-main-div" @click="showAll">
<el-row> <el-row>
<el-col> <el-col>
@ -14,28 +14,28 @@
<el-form :model="currentScenario" label-position="right" label-width="80px" size="small" :rules="rules" ref="currentScenario" style="margin-right: 20px"> <el-form :model="currentScenario" label-position="right" label-width="80px" size="small" :rules="rules" ref="currentScenario" style="margin-right: 20px">
<!-- 基础信息 --> <!-- 基础信息 -->
<el-row> <el-row>
<el-col :span="12"> <el-col :span="7">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input class="ms-scenario-input" size="small" v-model="currentScenario.name"/> <el-input class="ms-scenario-input" size="small" v-model="currentScenario.name"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="7">
<el-form-item :label="$t('test_track.module.module')" prop="apiScenarioModuleId"> <el-form-item :label="$t('test_track.module.module')" prop="apiScenarioModuleId">
<el-select class="ms-scenario-input" size="small" v-model="currentScenario.apiScenarioModuleId"> <el-select class="ms-scenario-input" size="small" v-model="currentScenario.apiScenarioModuleId">
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/> <el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> <el-col :span="7">
<el-row>
<el-col :span="12">
<el-form-item :label="$t('commons.status')" prop="status"> <el-form-item :label="$t('commons.status')" prop="status">
<el-select class="ms-scenario-input" size="small" v-model="currentScenario.status"> <el-select class="ms-scenario-input" size="small" v-model="currentScenario.status">
<el-option v-for="item in options" :key="item.id" :label="item.label" :value="item.id"/> <el-option v-for="item in options" :key="item.id" :label="item.label" :value="item.id"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> </el-row>
<el-row>
<el-col :span="7">
<el-form-item :label="$t('api_test.definition.request.responsible')" prop="principal"> <el-form-item :label="$t('api_test.definition.request.responsible')" prop="principal">
<el-select v-model="currentScenario.principal" <el-select v-model="currentScenario.principal"
:placeholder="$t('api_test.definition.request.responsible')" filterable size="small" :placeholder="$t('api_test.definition.request.responsible')" filterable size="small"
@ -47,20 +47,16 @@
:value="item.id"> :value="item.id">
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> <el-col :span="7">
<el-row>
<el-col :span="12">
<el-form-item :label="$t('test_track.case.priority')" prop="level"> <el-form-item :label="$t('test_track.case.priority')" prop="level">
<el-select class="ms-scenario-input" size="small" v-model="currentScenario.level"> <el-select class="ms-scenario-input" size="small" v-model="currentScenario.level">
<el-option v-for="item in levels" :key="item.id" :label="item.label" :value="item.id"/> <el-option v-for="item in levels" :key="item.id" :label="item.label" :value="item.id"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="7">
<el-form-item :label="$t('api_test.automation.follow_people')" prop="followPeople"> <el-form-item :label="$t('api_test.automation.follow_people')" prop="followPeople">
<el-select v-model="currentScenario.followPeople" <el-select v-model="currentScenario.followPeople"
:placeholder="$t('api_test.automation.follow_people')" filterable size="small" :placeholder="$t('api_test.automation.follow_people')" filterable size="small"
@ -76,14 +72,13 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="7">
<el-form-item label="Tag" prop="tags"> <el-form-item label="Tag" prop="tags">
<ms-input-tag :currentScenario="currentScenario" ref="tag"/> <ms-input-tag :currentScenario="currentScenario" ref="tag"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="7">
<el-form-item :label="$t('commons.description')" prop="description"> <el-form-item :label="$t('commons.description')" prop="description">
<el-input class="ms-http-textarea" <el-input class="ms-http-textarea"
v-model="currentScenario.description" v-model="currentScenario.description"
@ -181,9 +176,9 @@
</div> </div>
</el-col> </el-col>
<!-- 按钮列表 --> <!-- 按钮列表 -->
<div>
<el-col :span="3"> <el-col :span="3">
<vue-fab id="fab" mainBtnColor="#783887" :click-auto-close="false"> <vue-fab id="fab" mainBtnColor="#783887" size="small" :global-options="globalOptions"
:click-auto-close="false" v-outside-click="outsideClick">
<fab-item <fab-item
v-for="(item, index) in buttons" v-for="(item, index) in buttons"
:key="index" :key="index"
@ -196,16 +191,11 @@
@clickItem="item.click"/> @clickItem="item.click"/>
</vue-fab> </vue-fab>
</el-col> </el-col>
</div>
</el-row> </el-row>
</div> </div>
<!--接口列表--> <!--接口列表-->
<el-drawer :visible.sync="apiListVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :modal="false" size="90%"> <scenario-api-relevance @save="pushApiOrCase" ref="scenarioApiRelevance"/>
<ms-api-definition :visible="visibleRef" :currentRow="currentRow"/>
<el-button style="float: right;margin: 0px 20px 0px" type="primary" @click="pushApiOrCase('REF')">{{$t('api_test.scenario.reference')}}</el-button>
<el-button style="float: right;" type="primary" @click="pushApiOrCase('Copy')">{{ $t('commons.copy') }}</el-button>
</el-drawer>
<!--自定义接口--> <!--自定义接口-->
<el-drawer :visible.sync="customizeVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('api_test.automation.customize_req')" style="overflow: auto" :modal="false" size="90%"> <el-drawer :visible.sync="customizeVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('api_test.automation.customize_req')" style="overflow: auto" :modal="false" size="90%">
@ -245,7 +235,6 @@
import MsIfController from "./IfController"; import MsIfController from "./IfController";
import MsApiAssertions from "../../definition/components/assertion/ApiAssertions"; import MsApiAssertions from "../../definition/components/assertion/ApiAssertions";
import MsApiExtract from "../../definition/components/extract/ApiExtract"; import MsApiExtract from "../../definition/components/extract/ApiExtract";
import MsApiDefinition from "./api/ApiDefinition";
import MsApiComponent from "./ApiComponent"; import MsApiComponent from "./ApiComponent";
import {ELEMENTS, ELEMENT_TYPE} from "./Setting"; import {ELEMENTS, ELEMENT_TYPE} from "./Setting";
import MsApiCustomize from "./ApiCustomize"; import MsApiCustomize from "./ApiCustomize";
@ -260,6 +249,8 @@
import ApiImport from "../../definition/components/import/ApiImport"; import ApiImport from "../../definition/components/import/ApiImport";
import InputTag from 'vue-input-tag' import InputTag from 'vue-input-tag'
import "@/common/css/material-icons.css" import "@/common/css/material-icons.css"
import OutsideClick from "@/common/js/outside-click";
import ScenarioApiRelevance from "./api/ScenarioApiRelevance";
export default { export default {
name: "EditApiScenario", name: "EditApiScenario",
@ -268,6 +259,7 @@
currentScenario: {}, currentScenario: {},
}, },
components: { components: {
ScenarioApiRelevance,
ApiEnvironmentConfig, ApiEnvironmentConfig,
MsScenarioParameters, MsScenarioParameters,
MsApiReportDetail, MsApiReportDetail,
@ -279,7 +271,6 @@
MsIfController, MsIfController,
MsApiAssertions, MsApiAssertions,
MsApiExtract, MsApiExtract,
MsApiDefinition,
MsApiComponent, MsApiComponent,
MsApiCustomize, MsApiCustomize,
ApiImport, ApiImport,
@ -323,8 +314,10 @@
debugData: {}, debugData: {},
reportId: "", reportId: "",
projectId: "", projectId: "",
visibleRef: "",
enableCookieShare: false, enableCookieShare: false,
globalOptions: {
spacing: 30
}
} }
} }
, ,
@ -340,6 +333,7 @@
, ,
watch: {} watch: {}
, ,
directives: {OutsideClick},
computed: { computed: {
buttons() { buttons() {
let buttons = [ let buttons = [
@ -447,7 +441,7 @@
}, },
methods: { methods: {
getIdx(index) { getIdx(index) {
return -1 * index - 2.25; // return index - 0.33
}, },
showButton(...names) { showButton(...names) {
for (const name of names) { for (const name of names) {
@ -457,6 +451,9 @@
} }
return false; return false;
}, },
outsideClick() {
this.operatingElements = ELEMENTS.get("ALL");
},
addComponent(type) { addComponent(type) {
switch (type) { switch (type) {
case ELEMENT_TYPE.IfController: case ELEMENT_TYPE.IfController:
@ -518,8 +515,7 @@
} }
, ,
apiListImport() { apiListImport() {
this.visibleRef = getUUID(); this.$refs.scenarioApiRelevance.open();
this.apiListVisible = true;
} }
, ,
recursiveSorting(arr) { recursiveSorting(arr) {
@ -599,20 +595,10 @@
} }
} }
, ,
pushApiOrCase(referenced) { pushApiOrCase(data, refType, referenced) {
if (this.currentRow.cases.length === 0 && this.currentRow.apis.length === 0) { data.forEach(item => {
this.$warning(this.$t('api_test.automation.reference_info')); this.setApiParameter(item, refType, referenced);
return; });
}
this.currentRow.cases.forEach(item => {
this.setApiParameter(item, "CASE", referenced);
})
this.currentRow.apis.forEach(item => {
this.setApiParameter(item, "API", referenced);
})
this.apiListVisible = false;
this.currentRow.cases = [];
this.currentRow.apis = [];
this.sort(); this.sort();
this.reload(); this.reload();
} }
@ -896,6 +882,10 @@
</script> </script>
<style scoped> <style scoped>
.card-content {
height: calc(100vh - 196px);
overflow-y: auto;
}
.ms-scenario-input { .ms-scenario-input {
width: 100%; width: 100%;
} }
@ -910,10 +900,9 @@
} }
.ms-debug-div { .ms-debug-div {
margin-left: 20px;
border: 1px #DCDFE6 solid; border: 1px #DCDFE6 solid;
border-radius: 4px; border-radius: 4px;
margin-right: 10px; margin-right: 20px;
} }
.ms-scenario-button { .ms-scenario-button {
@ -946,7 +935,6 @@
} }
#fab { #fab {
bottom: unset;
right: 90px; right: 90px;
z-index: 5; z-index: 5;
} }

View File

@ -0,0 +1,165 @@
<template>
<el-dialog class="api-relevance" :title="$t('test_track.plan_view.relevance_test_case')"
:visible.sync="dialogVisible"
width="60%"
:close-on-click-modal="false"
top="50px">
<ms-container>
<ms-aside-container :enable-aside-hidden="false">
<ms-api-module
@nodeSelectEvent="nodeChange"
@protocolChange="handleProtocolChange"
@refreshTable="refresh"
@setModuleOptions="setModuleOptions"
:is-read-only="true"
ref="nodeTree"/>
</ms-aside-container>
<ms-main-container>
<scenario-relevance-api-list
v-if="isApiListEnable"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange"
ref="apiList"
/>
<scenario-relevance-case-list
v-if="!isApiListEnable"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange"
ref="apiCaseList"/>
</ms-main-container>
</ms-container>
<template v-slot:footer>
<el-button type="primary" @click="copy" @keydown.enter.native.prevent>复制</el-button>
<el-button v-if="!isApiListEnable" type="primary" @click="reference" @keydown.enter.native.prevent>引用</el-button>
</template>
</el-dialog>
</template>
<script>
import ScenarioRelevanceCaseList from "./ScenarioRelevanceCaseList";
import MsApiModule from "../../../definition/components/module/ApiModule";
import MsContainer from "../../../../common/components/MsContainer";
import MsAsideContainer from "../../../../common/components/MsAsideContainer";
import MsMainContainer from "../../../../common/components/MsMainContainer";
import ScenarioRelevanceApiList from "./ScenarioRelevanceApiList";
export default {
name: "ScenarioApiRelevance",
components: {
ScenarioRelevanceApiList,
MsMainContainer, MsAsideContainer, MsContainer, MsApiModule, ScenarioRelevanceCaseList},
data() {
return {
dialogVisible: false,
result: {},
currentProtocol: null,
selectNodeIds: [],
moduleOptions: {},
isApiListEnable: true,
}
},
methods: {
reference() {
this.save('REF');
},
copy() {
this.save('Copy');
},
save(reference) {
if (this.isApiListEnable) {
this.$emit('save', this.$refs.apiList.selectRows, 'API', reference);
this.close();
} else {
let apiCases = this.$refs.apiCaseList.selectRows;
let ids = Array.from(apiCases).map(row => row.id);
this.result = this.$post("/api/testcase/get/request", {ids: ids}, (response) => {
apiCases.forEach((item) => {
item.request = response.data[item.id];
});
this.$emit('save', apiCases, 'CASE', reference);
this.close();
});
}
},
close() {
this.refresh();
this.dialogVisible = false;
},
open() {
this.dialogVisible = true;
},
isApiListEnableChange(data) {
this.isApiListEnable = data;
},
nodeChange(node, nodeIds, pNodes) {
this.selectNodeIds = nodeIds;
},
handleProtocolChange(protocol) {
this.currentProtocol = protocol;
},
setModuleOptions(data) {
this.moduleOptions = data;
},
saveCaseRelevance() {
let param = {};
let url = '';
let environmentId = undefined;
let selectIds = [];
if (this.isApiListEnable) {
url = '/api/definition/relevance';
environmentId = this.$refs.apiList.environmentId;
selectIds = Array.from(this.$refs.apiList.selectRows).map(row => row.id);
} else {
url = '/api/testcase/relevance';
environmentId = this.$refs.apiCaseList.environmentId;
selectIds = Array.from(this.$refs.apiCaseList.selectRows).map(row => row.id);
}
if (!environmentId) {
this.$warning(this.$t('api_test.environment.select_environment'));
return;
}
param.planId = this.planId;
param.selectIds = selectIds;
param.environmentId = environmentId;
this.result = this.$post(url, param, () => {
this.$success(this.$t('commons.save_success'));
this.$emit('refresh');
this.refresh();
this.$refs.baseRelevance.close();
});
},
refresh() {
if (this.isApiListEnable) {
this.$refs.apiList.initTable();
} else {
this.$refs.apiCaseList.initTable();
}
},
}
}
</script>
<style scoped>
.ms-aside-container {
border: 0px;
}
.api-relevance >>> .el-dialog__body {
padding: 10px 20px;
}
</style>

View File

@ -0,0 +1,248 @@
<template>
<api-list-container
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange">
<el-input placeholder="搜索" @blur="initTable" class="search-input" size="small" @keyup.enter.native="initTable" 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">
<el-table-column type="selection"/>
<el-table-column prop="name" :label="$t('api_test.definition.api_name')" show-overflow-tooltip/>
<el-table-column
prop="status"
column-key="api_status"
:label="$t('api_test.definition.api_status')"
show-overflow-tooltip>
<template v-slot:default="scope">
<ms-tag v-if="scope.row.status == 'Prepare'" type="info" effect="plain" :content="$t('test_track.plan.plan_status_prepare')"/>
<ms-tag v-if="scope.row.status == 'Underway'" type="warning" effect="plain" :content="$t('test_track.plan.plan_status_running')"/>
<ms-tag v-if="scope.row.status == 'Completed'" type="success" effect="plain" :content="$t('test_track.plan.plan_status_completed')"/>
<ms-tag v-if="scope.row.status == 'Trash'" type="danger" effect="plain" content="废弃"/>
</template>
</el-table-column>
<el-table-column
prop="method"
:label="$t('api_test.definition.api_type')"
show-overflow-tooltip>
<template v-slot:default="scope" class="request-method">
<el-tag size="mini" :style="{'background-color': getColor(scope.row.method), border: getColor(true, scope.row.method)}" class="api-el-tag">
{{ scope.row.method}}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="path"
:label="$t('api_test.definition.api_path')"
show-overflow-tooltip/>
<el-table-column
prop="userName"
:label="$t('api_test.definition.api_principal')"
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
prop="caseTotal"
:label="$t('api_test.definition.api_case_number')"
show-overflow-tooltip/>
<el-table-column
prop="caseStatus"
:label="$t('api_test.definition.api_case_status')"
show-overflow-tooltip/>
<el-table-column
prop="casePassingRate"
:label="$t('api_test.definition.api_case_passing_rate')"
show-overflow-tooltip/>
</el-table>
<ms-table-pagination :change="initTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</api-list-container>
</template>
<script>
import MsTableOperator from "../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import MsTablePagination from "../../../../common/pagination/TablePagination";
import MsTag from "../../../../common/components/MsTag";
import MsBottomContainer from "../../../definition/components/BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../../../definition/components/basis/BatchEdit";
import {API_METHOD_COLOUR, CASE_PRIORITY} from "../../../definition/model/JsonData";
import {getCurrentProjectID} from "@/common/js/utils";
import ApiListContainer from "../../../definition/components/list/ApiListContainer";
import PriorityTableItem from "../../../../track/common/tableItems/planview/PriorityTableItem";
import {_filter, _sort} from "../../../../../../common/js/utils";
import {_handleSelect, _handleSelectAll} from "../../../../../../common/js/tableUtils";
export default {
name: "ScenarioRelevanceApiList",
components: {
PriorityTableItem,
ApiListContainer,
MsTableOperatorButton,
MsTableOperator,
MsTablePagination,
MsTag,
MsBottomContainer,
ShowMoreBtn,
MsBatchEdit
},
data() {
return {
condition: {},
selectCase: {},
result: {},
moduleId: "",
deletePath: "/test/case/delete",
selectRows: new Set(),
typeArr: [
{id: 'priority', name: this.$t('test_track.case.priority')},
],
priorityFilters: [
{text: 'P0', value: 'P0'},
{text: 'P1', value: 'P1'},
{text: 'P2', value: 'P2'},
{text: 'P3', value: 'P3'}
],
valueArr: {
priority: CASE_PRIORITY,
},
methodColorMap: new Map(API_METHOD_COLOUR),
tableData: [],
currentPage: 1,
pageSize: 10,
total: 0,
}
},
props: {
currentProtocol: String,
selectNodeIds: Array,
visible: {
type: Boolean,
default: false,
},
isApiListEnable: {
type: Boolean,
default: false,
},
isReadOnly: {
type: Boolean,
default: false
},
isCaseRelevance: {
type: Boolean,
default: false,
},
relevanceProjectId: String,
planId: String
},
created: function () {
this.initTable();
},
watch: {
selectNodeIds() {
this.initTable();
},
currentProtocol() {
this.initTable();
},
},
computed: {
},
methods: {
isApiListEnableChange(data) {
this.$emit('isApiListEnableChange', data);
},
initTable() {
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/definition/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => {
this.total = response.data.itemCount;
this.tableData = response.data.listObject;
});
},
handleSelect(selection, row) {
_handleSelect(this, selection, row, this.selectRows);
},
showExecResult(row) {
this.visible = false;
this.$emit('showExecResult', row);
},
filter(filters) {
_filter(filters, this.condition);
this.initTable();
},
sort(column) {
//
if (this.condition.orders) {
this.condition.orders = [];
}
_sort(column, this.condition);
this.initTable();
},
handleSelectAll(selection) {
_handleSelectAll(this, selection, this.tableData, this.selectRows);
},
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
getColor(method) {
return this.methodColorMap.get(method);
},
},
}
</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

@ -0,0 +1,239 @@
<template>
<div>
<api-list-container
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange">
<el-input placeholder="搜索" @blur="initTable" @keyup.enter.native="initTable" 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"
@filter-change="filter"
@sort-change="sort"
@select="handleSelect">
<el-table-column type="selection"/>
<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="createUser"
:label="'创建人'"
show-overflow-tooltip/>
<el-table-column
sortable="custom"
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>
<ms-table-pagination :change="initTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</api-list-container>
</div>
</template>
<script>
import MsTableOperator from "../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import MsTablePagination from "../../../../common/pagination/TablePagination";
import MsTag from "../../../../common/components/MsTag";
import MsBottomContainer from "../../../definition/components/BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../../../definition/components/basis/BatchEdit";
import {API_METHOD_COLOUR, CASE_PRIORITY} from "../../../definition/model/JsonData";
import {getCurrentProjectID} from "@/common/js/utils";
import ApiListContainer from "../../../definition/components/list/ApiListContainer";
import PriorityTableItem from "../../../../track/common/tableItems/planview/PriorityTableItem";
import {_filter, _sort} from "../../../../../../common/js/utils";
import {_handleSelect, _handleSelectAll} from "../../../../../../common/js/tableUtils";
export default {
name: "ScenarioRelevanceCaseList",
components: {
PriorityTableItem,
ApiListContainer,
MsTableOperatorButton,
MsTableOperator,
MsTablePagination,
MsTag,
MsBottomContainer,
ShowMoreBtn,
MsBatchEdit
},
data() {
return {
condition: {},
selectCase: {},
result: {},
moduleId: "",
selectRows: new Set(),
typeArr: [
{id: 'priority', name: this.$t('test_track.case.priority')},
],
priorityFilters: [
{text: 'P0', value: 'P0'},
{text: 'P1', value: 'P1'},
{text: 'P2', value: 'P2'},
{text: 'P3', value: 'P3'}
],
valueArr: {
priority: CASE_PRIORITY,
},
methodColorMap: new Map(API_METHOD_COLOUR),
tableData: [],
currentPage: 1,
pageSize: 10,
total: 0,
}
},
props: {
currentProtocol: String,
selectNodeIds: Array,
visible: {
type: Boolean,
default: false,
},
isApiListEnable: {
type: Boolean,
default: false,
},
isReadOnly: {
type: Boolean,
default: false
},
isCaseRelevance: {
type: Boolean,
default: false,
},
relevanceProjectId: String,
planId: String
},
created: function () {
this.initTable();
},
watch: {
selectNodeIds() {
this.initTable();
},
currentProtocol() {
this.initTable();
},
},
computed: {
},
methods: {
isApiListEnableChange(data) {
this.$emit('isApiListEnableChange', data);
},
initTable() {
this.selectRows = new Set();
this.condition.status = "";
this.condition.moduleIds = this.selectNodeIds;
if (this.currentProtocol != null) {
this.condition.protocol = this.currentProtocol;
}
this.condition.projectId = getCurrentProjectID();
this.result = this.$post("/api/testcase/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => {
this.total = response.data.itemCount;
this.tableData = response.data.listObject;
});
},
handleSelect(selection, row) {
_handleSelect(this, selection, row, this.selectRows);
},
showExecResult(row) {
this.visible = false;
this.$emit('showExecResult', row);
},
filter(filters) {
_filter(filters, this.condition);
this.initTable();
},
sort(column) {
//
if (this.condition.orders) {
this.condition.orders = [];
}
_sort(column, this.condition);
this.initTable();
},
handleSelectAll(selection) {
_handleSelectAll(this, selection, this.tableData, this.selectRows);
},
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
handleTestCase(testCase) {
this.$get('/api/definition/get/' + testCase.apiDefinitionId, (response) => {
let api = response.data;
let 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 = [];
}
selectApi.url = request.path;
this.$refs.caseList.open(selectApi, testCase.id);
});
},
},
}
</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

@ -273,7 +273,7 @@
} else if (this.isRelevanceModel) { } else if (this.isRelevanceModel) {
return '/api/testcase/delete/' + apiCase.id; return '/api/testcase/delete/' + apiCase.id;
} else { } else {
return '/api/testcase/delete/' + +apiCase.id; return '/api/testcase/delete/' + apiCase.id;
} }
}, },
// getMaintainerOptions() { // getMaintainerOptions() {

View File

@ -0,0 +1,31 @@
const OutsideClick = {
// 初始化指令
bind(el, binding, vnode) {
function documentHandler(e) {
// 这里判断点击的元素是否是本身,是本身,则返回
if (el.contains(e.target)) {
return false;
}
// 判断指令中是否绑定了函数
if (binding.expression) {
// 如果绑定了函数 则调用那个函数此处binding.value就是handleClose方法
binding.value(e);
}
}
// 给当前元素绑定个私有变量方便在unbind中可以解除事件监听
el.__vueClickOutside__ = documentHandler;
document.addEventListener('click', documentHandler);
},
update() {
},
unbind(el, binding) {
// 解除事件监听
document.removeEventListener('click', el.__vueClickOutside__);
delete el.__vueClickOutside__;
},
};
export default OutsideClick;