feat(接口定制): 基础保存功能完善

This commit is contained in:
fit2-zhao 2020-11-09 18:44:56 +08:00
parent 8404272115
commit 9bdb4f9625
26 changed files with 519 additions and 418 deletions

View File

@ -49,6 +49,11 @@ public class ApiDelimitController {
apiDelimitService.delete(id); apiDelimitService.delete(id);
} }
@PostMapping("/deleteBatch")
public void deleteBatch(@RequestBody List<String> ids) {
apiDelimitService.deleteBatch(ids);
}
@GetMapping("/get/{id}") @GetMapping("/get/{id}")
public ApiDelimit get(@PathVariable String id) { public ApiDelimit get(@PathVariable String id) {
return apiDelimitService.get(id); return apiDelimitService.get(id);

View File

@ -6,6 +6,7 @@ import io.metersphere.api.dto.delimit.ApiDelimitRequest;
import io.metersphere.api.dto.delimit.ApiDelimitResult; import io.metersphere.api.dto.delimit.ApiDelimitResult;
import io.metersphere.api.dto.delimit.SaveApiDelimitRequest; import io.metersphere.api.dto.delimit.SaveApiDelimitRequest;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiDelimitExecResultMapper;
import io.metersphere.base.mapper.ApiDelimitMapper; import io.metersphere.base.mapper.ApiDelimitMapper;
import io.metersphere.base.mapper.ApiTestFileMapper; import io.metersphere.base.mapper.ApiTestFileMapper;
import io.metersphere.commons.constants.APITestStatus; import io.metersphere.commons.constants.APITestStatus;
@ -43,6 +44,8 @@ public class ApiDelimitService {
private FileService fileService; private FileService fileService;
@Resource @Resource
private ApiTestCaseService apiTestCaseService; private ApiTestCaseService apiTestCaseService;
@Resource
private ApiDelimitExecResultMapper apiDelimitExecResultMapper;
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
@ -134,11 +137,19 @@ public class ApiDelimitService {
public void delete(String apiId) { public void delete(String apiId) {
apiTestCaseService.checkIsRelateTest(apiId); apiTestCaseService.checkIsRelateTest(apiId);
deleteFileByTestId(apiId); deleteFileByTestId(apiId);
//apiReportService.deleteByTestId(apiId); apiDelimitExecResultMapper.deleteByResourceId(apiId);
apiDelimitMapper.deleteByPrimaryKey(apiId); apiDelimitMapper.deleteByPrimaryKey(apiId);
deleteBodyFiles(apiId); deleteBodyFiles(apiId);
} }
public void deleteBatch(List<String> apiIds) {
// 简单处理后续优化
apiIds.forEach(item -> {
delete(item);
});
}
public void deleteBodyFiles(String apiId) { public void deleteBodyFiles(String apiId) {
File file = new File(BODY_FILE_DIR + "/" + apiId); File file = new File(BODY_FILE_DIR + "/" + apiId);
FileUtil.deleteContents(file); FileUtil.deleteContents(file);
@ -149,9 +160,9 @@ public class ApiDelimitService {
private void checkNameExist(SaveApiDelimitRequest request) { private void checkNameExist(SaveApiDelimitRequest request) {
ApiDelimitExample example = new ApiDelimitExample(); ApiDelimitExample example = new ApiDelimitExample();
example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId()).andIdNotEqualTo(request.getId()); example.createCriteria().andUrlEqualTo(request.getUrl()).andProjectIdEqualTo(request.getProjectId()).andIdNotEqualTo(request.getId());
if (apiDelimitMapper.countByExample(example) > 0) { if (apiDelimitMapper.countByExample(example) > 0) {
MSException.throwException(Translator.get("load_test_already_exists")); MSException.throwException(Translator.get("api_delimit_url_not_repeating"));
} }
} }

View File

@ -207,7 +207,7 @@ public class ApiModuleService {
if (level > 8) { if (level > 8) {
MSException.throwException(Translator.get("node_deep_limit")); MSException.throwException(Translator.get("node_deep_limit"));
} }
if (rootNode.getId().equals("rootID")) { if (rootNode.getId().equals("root")) {
rootPath = ""; rootPath = "";
} }
ApiModule apiDelimitNode = new ApiModule(); ApiModule apiDelimitNode = new ApiModule();

View File

@ -5,6 +5,7 @@ import io.metersphere.api.dto.delimit.ApiTestCaseRequest;
import io.metersphere.api.dto.delimit.ApiTestCaseResult; import io.metersphere.api.dto.delimit.ApiTestCaseResult;
import io.metersphere.api.dto.delimit.SaveApiTestCaseRequest; import io.metersphere.api.dto.delimit.SaveApiTestCaseRequest;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiDelimitExecResultMapper;
import io.metersphere.base.mapper.ApiTestCaseMapper; import io.metersphere.base.mapper.ApiTestCaseMapper;
import io.metersphere.base.mapper.ApiTestFileMapper; import io.metersphere.base.mapper.ApiTestFileMapper;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
@ -38,6 +39,8 @@ public class ApiTestCaseService {
private ApiTestFileMapper apiTestFileMapper; private ApiTestFileMapper apiTestFileMapper;
@Resource @Resource
private FileService fileService; private FileService fileService;
@Resource
private ApiDelimitExecResultMapper apiDelimitExecResultMapper;
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
@ -110,7 +113,7 @@ public class ApiTestCaseService {
public void delete(String testId) { public void delete(String testId) {
deleteFileByTestId(testId); deleteFileByTestId(testId);
//apiReportService.deleteByTestId(testId); apiDelimitExecResultMapper.deleteByResourceId(testId);
apiTestCaseMapper.deleteByPrimaryKey(testId); apiTestCaseMapper.deleteByPrimaryKey(testId);
deleteBodyFiles(testId); deleteBodyFiles(testId);
} }

View File

@ -264,6 +264,11 @@ public class ApiDelimitExample {
return (Criteria) this; return (Criteria) this;
} }
public Criteria andUrlEqualTo(String value) {
addCriterion("url =", value, "url");
return (Criteria) this;
}
public Criteria andNameNotEqualTo(String value) { public Criteria andNameNotEqualTo(String value) {
addCriterion("name <>", value, "name"); addCriterion("name <>", value, "name");
return (Criteria) this; return (Criteria) this;

View File

@ -0,0 +1,18 @@
package io.metersphere.base.domain;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApiDelimitExecResult implements Serializable {
private String id;
private String resourceId;
private String name;
private String content;
private String status;
private String userId;
private String startTime;
private String endTime;
}

View File

@ -0,0 +1,10 @@
package io.metersphere.base.mapper;
import io.metersphere.base.domain.ApiDelimitExecResult;
public interface ApiDelimitExecResultMapper {
int deleteByResourceId(String id);
int insert(ApiDelimitExecResult record);
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.ApiDelimitExecResultMapper">
<delete id="deleteByResourceId" parameterType="java.lang.String">
delete from api_delimit_exec_result where resource_id = #{id,jdbcType=VARCHAR}
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.ApiDelimitExecResult">
insert into api_delimit_exec_result
(id, resource_id,name,content, status, user_id, start_time, end_time)
values
(#{id,jdbcType=VARCHAR}, #{resourceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{content,jdbcType=LONGVARCHAR}, #{status,jdbcType=VARCHAR},
#{startTime,jdbcType=BIGINT}, #{endTime,jdbcType=BIGINT})
</insert>
</mapper>

View File

@ -260,7 +260,7 @@
request = #{request,jdbcType=LONGVARCHAR}, request = #{request,jdbcType=LONGVARCHAR},
</if> </if>
<if test="response != null"> <if test="response != null">
request = #{response,jdbcType=LONGVARCHAR}, response = #{response,jdbcType=LONGVARCHAR},
</if> </if>
</set> </set>

View File

@ -1,13 +0,0 @@
CREATE TABLE IF NOT EXISTS `api_module` (
`id` varchar(50) NOT NULL COMMENT 'Test case node ID',
`project_id` varchar(50) NOT NULL COMMENT 'Project ID this node belongs to',
`name` varchar(64) NOT NULL COMMENT 'Node name',
`protocol` varchar(64) NOT NULL COMMENT 'Node protocol',
`parent_id` varchar(50) DEFAULT NULL COMMENT 'Parent node ID',
`level` int(10) DEFAULT 1 COMMENT 'Node level',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

View File

@ -169,6 +169,7 @@ test_plan_notification=Test plan notification
task_defect_notification=Task defect notification task_defect_notification=Task defect notification
task_notification=Jenkins Task notification task_notification=Jenkins Task notification
task_notification_=Timing task result notification task_notification_=Timing task result notification
api_delimit_url_not_repeating=The interface request address already exists

View File

@ -170,3 +170,4 @@ test_plan_notification=测试计划通知
task_defect_notification=缺陷任务通知 task_defect_notification=缺陷任务通知
task_notification=jenkins任务通知 task_notification=jenkins任务通知
task_notification_=定时任务结果通知 task_notification_=定时任务结果通知
api_delimit_url_not_repeating=接口请求地址已经存在

View File

@ -171,4 +171,4 @@ test_plan_notification=測試計畫通知
task_defect_notification=缺陷任務通知 task_defect_notification=缺陷任務通知
task_notification=jenkins任務通知 task_notification=jenkins任務通知
task_notification_=定時任務通知 task_notification_=定時任務通知
api_delimit_url_not_repeating=接口請求地址已經存在

View File

@ -2,8 +2,8 @@
<ms-container> <ms-container>
<ms-aside-container> <ms-aside-container>
<ms-node-tree @selectModule="selectModule" @getApiModuleTree="initTree" <ms-node-tree @selectModule="selectModule" @getApiModuleTree="initTree" @changeProject="changeProject"
@changeProject="changeProject"></ms-node-tree> @refresh="refresh"/>
</ms-aside-container> </ms-aside-container>
<ms-main-container> <ms-main-container>
@ -16,12 +16,11 @@
<el-dropdown-item command="closeAll">{{$t('api_test.delimit.request.close_all_label')}}</el-dropdown-item> <el-dropdown-item command="closeAll">{{$t('api_test.delimit.request.close_all_label')}}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 主框架列表 -->
<el-tabs v-model="editableTabsValue" @edit="handleTabsEdit"> <el-tabs v-model="apiDefaultTab" @edit="handleTabsEdit">
<el-tab-pane <el-tab-pane
:key="item.name" :key="item.name"
v-for="(item) in editableTabs" v-for="(item) in apiTabs"
:label="item.title" :label="item.title"
:closable="item.closable" :closable="item.closable"
:name="item.name"> :name="item.name">
@ -32,20 +31,23 @@
:current-module="currentModule" :current-module="currentModule"
@editApi="editApi" @editApi="editApi"
@handleCase="handleCase" @handleCase="handleCase"
ref="apiList"> ref="apiList"/>
</ms-api-list>
<!-- 添加测试窗口-->
<div v-else-if="item.type=== 'add'"> <div v-else-if="item.type=== 'add'">
<ms-api-config @runTest="runTest" @saveApi="saveApi" :current-api="currentApi" <ms-api-config @runTest="runTest" @saveApi="saveApi" :current-api="currentApi"
:currentProject="currentProject" :currentProject="currentProject"
:moduleOptions="moduleOptions" ref="apiConfig"></ms-api-config> :moduleOptions="moduleOptions" ref="apiConfig"/>
</div> </div>
<!-- 快捷调试 -->
<div v-else-if="item.type=== 'debug'"> <div v-else-if="item.type=== 'debug'">
<ms-debug-http-page @saveAs="saveAs"></ms-debug-http-page> <ms-debug-http-page @saveAs="editApi"/>
</div> </div>
<!-- 测试-->
<div v-else-if="item.type=== 'test'"> <div v-else-if="item.type=== 'test'">
<!-- 测试--> <ms-run-test-http-page :api-data="runTestData" @saveAsApi="editApi"/>
<ms-run-test-http-page></ms-run-test-http-page>
</div> </div>
</el-tab-pane> </el-tab-pane>
@ -83,21 +85,16 @@
comments: {}, comments: {},
data() { data() {
return { return {
projectId: null,
isHide: true, isHide: true,
editableTabsValue: '1', apiDefaultTab: 'default',
tabIndex: 1,
result: {},
currentPage: 1,
pageSize: 5,
total: 0,
currentProject: null, currentProject: null,
currentModule: null, currentModule: null,
currentApi: {}, currentApi: {},
moduleOptions: {}, moduleOptions: {},
editableTabs: [{ runTestData: {},
apiTabs: [{
title: this.$t('api_test.delimit.api_title'), title: this.$t('api_test.delimit.api_title'),
name: '1', name: 'default',
type: "list", type: "list",
closable: false closable: false
}], }],
@ -106,21 +103,17 @@
methods: { methods: {
handleCommand(e) { handleCommand(e) {
if (e === "add") { if (e === "add") {
this.currentApi = { this.currentApi = {status: "Underway", path: "GET", userId: getCurrentUser().id};
status: "Underway",
path: "GET",
userId: getCurrentUser().id,
};
this.handleTabsEdit(this.$t('api_test.delimit.request.title'), e); this.handleTabsEdit(this.$t('api_test.delimit.request.title'), e);
} }
else if (e === "test") { else if (e === "test") {
this.handleTabsEdit(this.$t("commons.api"), e); this.handleTabsEdit(this.$t("commons.api"), e);
} }
else if (e === "closeAll") { else if (e === "closeAll") {
let tabs = this.editableTabs[0]; let tabs = this.apiTabs[0];
this.editableTabs = []; this.apiTabs = [];
this.editableTabsValue = tabs.name; this.apiDefaultTab = tabs.name;
this.editableTabs.push(tabs); this.apiTabs.push(tabs);
} }
else { else {
this.handleTabsEdit(this.$t('api_test.delimit.request.fast_debug'), "debug"); this.handleTabsEdit(this.$t('api_test.delimit.request.fast_debug'), "debug");
@ -128,8 +121,8 @@
}, },
handleTabsEdit(targetName, action) { handleTabsEdit(targetName, action) {
if (action === 'remove') { if (action === 'remove') {
let tabs = this.editableTabs; let tabs = this.apiTabs;
let activeName = this.editableTabsValue; let activeName = this.apiDefaultTab;
if (activeName === targetName) { if (activeName === targetName) {
tabs.forEach((tab, index) => { tabs.forEach((tab, index) => {
if (tab.name === targetName) { if (tab.name === targetName) {
@ -140,23 +133,22 @@
} }
}); });
} }
this.editableTabsValue = activeName; this.apiDefaultTab = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName); this.apiTabs = tabs.filter(tab => tab.name !== targetName);
} else { } else {
if (targetName === undefined || targetName === null) { if (targetName === undefined || targetName === null) {
targetName = this.$t('api_test.delimit.request.title'); targetName = this.$t('api_test.delimit.request.title');
} }
let newTabName = getUUID().substring(0, 8); let newTabName = getUUID().substring(0, 8);
this.editableTabs.push({ this.apiTabs.push({
title: targetName, title: targetName,
name: newTabName, name: newTabName,
closable: true, closable: true,
type: action type: action
}); });
this.editableTabsValue = newTabName; this.apiDefaultTab = newTabName;
} }
}, },
editApi(row) { editApi(row) {
this.currentApi = row; this.currentApi = row;
this.handleTabsEdit(row.name, "add"); this.handleTabsEdit(row.name, "add");
@ -172,24 +164,25 @@
this.currentModule = data; this.currentModule = data;
}, },
refresh(data) { refresh(data) {
this.$refs.apiList[0].initTableData(data); this.$refs.apiList[0].initApiTable(data);
}, },
saveAs(data) { setTabTitle(data) {
this.handleCommand("add"); for (let index in this.apiTabs) {
}, let tab = this.apiTabs[index];
runTest(data) { if (tab.name === this.apiDefaultTab) {
this.handleCommand("test");
},
saveApi(data) {
for (let index in this.editableTabs) {
let tab = this.editableTabs[index];
if (tab.name === this.editableTabsValue) {
tab.title = data.name; tab.title = data.name;
break; break;
} }
} }
this.runTestData = data;
},
runTest(data) {
this.setTabTitle(data);
this.handleCommand("test");
},
saveApi(data) {
this.setTabTitle(data);
this.$refs.apiList[0].initTableData(data); this.$refs.apiList[0].initTableData(data);
}, },
initTree(data) { initTree(data) {
this.moduleOptions = data; this.moduleOptions = data;

View File

@ -27,7 +27,7 @@
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<div> <div>
<el-select size="small" :placeholder="$t('api_test.delimit.request.grade_info')" v-model="grdValue" <el-select size="small" :placeholder="$t('api_test.delimit.request.grade_info')" v-model="priorityValue"
class="ms-api-header-select" @change="getApiTest"> class="ms-api-header-select" @change="getApiTest">
<el-option v-for="grd in priority" :key="grd.id" :label="grd.name" :value="grd.id"/> <el-option v-for="grd in priority" :key="grd.id" :label="grd.name" :value="grd.id"/>
</el-select> </el-select>
@ -72,16 +72,15 @@
</el-row> </el-row>
</el-card> </el-card>
<!-- 环境 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/> <api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
</el-header> </el-header>
<!-- 用例部分 --> <!-- 用例部分 -->
<el-main> <el-main>
<div v-for="(item,index) in apiCaseList" :key="index"> <div v-for="(item,index) in apiCaseList" :key="index">
<el-card style="margin-top: 10px"> <el-card style="margin-top: 10px" @click.native="selectTestCase(item,$event)">
<el-row> <el-row>
<el-col :span="5"> <el-col :span="5">
@ -100,8 +99,7 @@
@click="active(item)"/> @click="active(item)"/>
<el-input v-if="item.type==='create'" size="small" v-model="item.name" :name="index" <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"/> :key="index" class="ms-api-header-select" style="width: 180px"/>
{{item.type!= 'create'? item.name : ''}} {{item.type!= 'create' ? item.name:''}}
<div v-if="item.type!='create'" style="color: #999999;font-size: 12px"> <div v-if="item.type!='create'" style="color: #999999;font-size: 12px">
<span> {{item.createTime | timestampFormatDate }}</span> {{item.createUser}} 创建 <span> {{item.createTime | timestampFormatDate }}</span> {{item.createUser}} 创建
<span> {{item.updateTime | timestampFormatDate }}</span> {{item.updateUser}} 更新 <span> {{item.updateTime | timestampFormatDate }}</span> {{item.updateUser}} 更新
@ -128,8 +126,7 @@
<!-- 请求参数--> <!-- 请求参数-->
<el-collapse-transition> <el-collapse-transition>
<div v-if="item.active"> <div v-if="item.active">
<ms-api-request-form :is-read-only="isReadOnly" :debug-report-id="debugReportId" @runDebug="runDebug" <ms-api-request-form :is-read-only="isReadOnly" :request="item.test.request"/>
:request="item.test.request"/>
<!-- 保存操作 --> <!-- 保存操作 -->
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)"> <el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)">
{{$t('commons.save')}} {{$t('commons.save')}}
@ -153,6 +150,7 @@
import {downloadFile, getUUID} from "@/common/js/utils"; import {downloadFile, getUUID} from "@/common/js/utils";
import {parseEnvironment} from "../model/EnvironmentModel"; import {parseEnvironment} from "../model/EnvironmentModel";
import ApiEnvironmentConfig from "../../test/components/ApiEnvironmentConfig"; import ApiEnvironmentConfig from "../../test/components/ApiEnvironmentConfig";
import {PRIORITY} from "../model/JsonData";
export default { export default {
name: 'ApiCaseList', name: 'ApiCaseList',
@ -173,18 +171,12 @@
grades: [], grades: [],
environments: [], environments: [],
envValue: {}, envValue: {},
grdValue: "",
value: "",
isReadOnly: false,
name: "", name: "",
priority: [ priorityValue: "",
{name: 'P0', id: 'P0'}, isReadOnly: false,
{name: 'P1', id: 'P1'}, selectedEvent: Object,
{name: 'P2', id: 'P2'}, priority: PRIORITY,
{name: 'P3', id: 'P3'}
],
apiCaseList: [], apiCaseList: [],
debugReportId: ''
} }
}, },
@ -197,16 +189,10 @@
this.getEnvironments(); this.getEnvironments();
} }
}, },
created() {
this.getApiTest();
},
methods: { methods: {
getRequest(request) {
if (request === undefined || request === null) {
this.test = new Test();
this.test.request.environment = this.envValue;
return this.test.request;
} else {
return new RequestFactory(JSON.parse(request));
}
},
getResult(data) { getResult(data) {
if (data === 'PASS') { if (data === 'PASS') {
return '执行结果:通过'; return '执行结果:通过';
@ -249,9 +235,6 @@
test: test, test: test,
}; };
this.apiCaseList.push(obj); this.apiCaseList.push(obj);
},
runDebug() {
}, },
active(item) { active(item) {
item.active = !item.active; item.active = !item.active;
@ -281,9 +264,9 @@
let condition = {}; let condition = {};
condition.projectId = this.api.projectId; condition.projectId = this.api.projectId;
condition.apiDelimitId = this.api.id; condition.apiDelimitId = this.api.id;
condition.priority = this.grdValue; condition.priority = this.priorityValue;
condition.name = this.name; condition.name = this.name;
this.result = this.$post("/api/testcase/list", condition, response => { this.$post("/api/testcase/list", condition, response => {
for (let index in response.data) { for (let index in response.data) {
let test = response.data[index]; let test = response.data[index];
test.test = new Test({request: new RequestFactory(JSON.parse(test.request))}); test.test = new Test({request: new RequestFactory(JSON.parse(test.request))});
@ -308,7 +291,6 @@
row.projectId = this.api.projectId; row.projectId = this.api.projectId;
row.apiDelimitId = this.api.id; row.apiDelimitId = this.api.id;
row.request = row.test.request; row.request = row.test.request;
let jmx = row.test.toJMX(); let jmx = row.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);
@ -323,7 +305,7 @@
}, },
getEnvironments() { getEnvironments() {
if (this.currentProject) { if (this.currentProject) {
this.result = this.$get('/api/environment/list/' + this.currentProject.id, response => { this.$get('/api/environment/list/' + this.currentProject.id, response => {
this.environments = response.data; this.environments = response.data;
this.environments.forEach(environment => { this.environments.forEach(environment => {
parseEnvironment(environment); parseEnvironment(environment);
@ -363,6 +345,23 @@
}, },
environmentConfigClose() { environmentConfigClose() {
this.getEnvironments(); this.getEnvironments();
},
selectTestCase(item, $event) {
if (item.type === "create") {
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);
}
} }
} }
} }
@ -413,4 +412,8 @@
.icon.is-active { .icon.is-active {
transform: rotate(90deg); transform: rotate(90deg);
} }
.is-selected {
background: #EFF7FF;
}
</style> </style>

View File

@ -1,6 +1,7 @@
<template> <template>
<div class="card-container"> <div class="card-container">
<!-- HTTP 请求参数 -->
<ms-add-complete-http-api @runTest="runTest" @saveApi="saveApi" :httpData="currentApi" :test="test" <ms-add-complete-http-api @runTest="runTest" @saveApi="saveApi" :httpData="currentApi" :test="test"
:moduleOptions="moduleOptions" :currentProject="currentProject" :moduleOptions="moduleOptions" :currentProject="currentProject"
v-if="reqType === 'HTTP'"></ms-add-complete-http-api> v-if="reqType === 'HTTP'"></ms-add-complete-http-api>
@ -10,7 +11,7 @@
<script> <script>
import MsAddCompleteHttpApi from "./complete/AddCompleteHttpApi"; import MsAddCompleteHttpApi from "./complete/AddCompleteHttpApi";
import {RequestFactory, Test} from "../model/ScenarioModel"; import {RequestFactory, Test} from "../model/ScenarioModel";
import {downloadFile, getUUID} from "@/common/js/utils"; import {getUUID} from "@/common/js/utils";
export default { export default {
name: "ApiConfig", name: "ApiConfig",
@ -18,8 +19,8 @@
data() { data() {
return { return {
reqType: RequestFactory.TYPES.HTTP, reqType: RequestFactory.TYPES.HTTP,
reqUrl: "",
test: new Test(), test: new Test(),
postUrl: "",
} }
}, },
props: { props: {
@ -28,30 +29,44 @@
currentProject: {}, currentProject: {},
}, },
created() { created() {
this.test = new Test({
request: this.currentApi.request != null ? new RequestFactory(JSON.parse(this.currentApi.request)) : null
});
if (this.currentApi != null && this.currentApi.id != null) { if (this.currentApi != null && this.currentApi.id != null) {
let item = this.currentApi; this.reqUrl = "/api/delimit/update";
this.test = new Test({
id: item.id,
projectId: item.projectId,
name: item.name,
status: item.status,
path: item.path,
request: new RequestFactory(JSON.parse(item.request)),
});
this.postUrl = "/api/delimit/update";
} else { } else {
this.test = new Test(); this.reqUrl = "/api/delimit/create";
this.postUrl = "/api/delimit/create"; this.currentApi.id = getUUID().substring(0, 8);
} }
}, },
methods: { methods: {
runTest(data) { runTest(data) {
this.$emit('runTest', data); if (this.editApi(data) === true) {
this.$emit('runTest', data);
}
}, },
getBodyUploadFiles() { saveApi(data) {
if (this.editApi(data) === true) {
this.$emit('saveApi', data);
}
},
editApi(data) {
data.projectId = this.currentProject.id;
data.request = data.test.request;
let bodyFiles = this.getBodyUploadFiles(data);
let jmx = data.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
this.$fileUpload(this.reqUrl, file, bodyFiles, data, () => {
this.$success(this.$t('commons.save_success'));
this.reqUrl = "/api/delimit/update";
return true;
});
},
getBodyUploadFiles(data) {
let bodyUploadFiles = []; let bodyUploadFiles = [];
this.test.bodyUploadIds = []; data.bodyUploadIds = [];
let request = this.test.request; let request = data.request;
if (request.body) { if (request.body) {
request.body.kvs.forEach(param => { request.body.kvs.forEach(param => {
if (param.files) { if (param.files) {
@ -60,7 +75,7 @@
let fileId = getUUID().substring(0, 8); let fileId = getUUID().substring(0, 8);
item.name = item.file.name; item.name = item.file.name;
item.id = fileId; item.id = fileId;
this.test.bodyUploadIds.push(fileId); data.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file); bodyUploadFiles.push(item.file);
} }
}); });
@ -69,18 +84,6 @@
} }
return bodyUploadFiles; return bodyUploadFiles;
}, },
saveApi(data) {
this.test = data;
let bodyFiles = this.getBodyUploadFiles();
let jmx = this.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
this.$fileUpload(this.postUrl, file, bodyFiles, this.test, () => {
this.$success(this.$t('commons.save_success'));
this.$emit('saveApi', this.test);
this.postUrl = "/api/delimit/update";
});
}
} }
} }
</script> </script>

View File

@ -1,42 +1,34 @@
<template> <template>
<div class="card-container"> <div class="card-container">
<el-card class="card-content" v-loading="result.loading"> <el-card class="card-content">
<template v-slot:header> <template v-slot:header>
<ms-table-header :showCreate="false" :condition.sync="condition" <ms-table-header :showCreate="false" :condition.sync="condition" @search="search"
@search="search"
:title="$t('api_test.delimit.api_title')"/> :title="$t('api_test.delimit.api_title')"/>
</template> </template>
<el-table <el-table border :data="tableData" row-key="id" class="test-content adjust-table"
border @select-all="handleSelectAll"
:data="tableData" @select="handleSelect">
row-key="id" <el-table-column type="selection"/>
class="test-content adjust-table"> <el-table-column width="40" :resizable="false" align="center">
<template v-slot:default="scope">
<el-table-column <show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectRows.size"/>
prop="name" </template>
:label="$t('api_test.delimit.api_name')" </el-table-column>
show-overflow-tooltip/>
<el-table-column prop="name" :label="$t('api_test.delimit.api_name')" show-overflow-tooltip/>
<el-table-column <el-table-column
prop="status" prop="status"
column-key="api_status" column-key="api_status"
:label="$t('api_test.delimit.api_status')" :label="$t('api_test.delimit.api_status')"
show-overflow-tooltip> show-overflow-tooltip>
<template v-slot:default="scope"> <template v-slot:default="scope">
<span class="el-dropdown-link"> <ms-tag v-if="scope.row.status == 'Prepare'" type="info"
<template> :content="$t('test_track.plan.plan_status_prepare')"/>
<div> <ms-tag v-if="scope.row.status == 'Underway'" type="primary"
<ms-tag v-if="scope.row.status == 'Prepare'" type="info" :content="$t('test_track.plan.plan_status_running')"/>
:content="$t('test_track.plan.plan_status_prepare')"/> <ms-tag v-if="scope.row.status == 'Completed'" type="success"
<ms-tag v-if="scope.row.status == 'Underway'" type="primary" :content="$t('test_track.plan.plan_status_completed')"/>
:content="$t('test_track.plan.plan_status_running')"/>
<ms-tag v-if="scope.row.status == 'Completed'" type="success"
:content="$t('test_track.plan.plan_status_completed')"/>
</div>
</template>
</span>
</template> </template>
</el-table-column> </el-table-column>
@ -44,17 +36,12 @@
prop="path" prop="path"
:label="$t('api_test.delimit.api_type')" :label="$t('api_test.delimit.api_type')"
show-overflow-tooltip> show-overflow-tooltip>
<template v-slot:default="scope"> <template v-slot:default="scope" class="request-method">
<span class="el-dropdown-link"> <el-tag size="mini"
<template> :style="{'background-color': getColor(true, scope.row.path)}" class="api-el-tag"> {{ scope.row.path
<div class="request-method"> }}
<el-tag size="mini" </el-tag>
:style="{'background-color': getColor(true, scope.row.path)}" class="api-el-tag"> {{ scope.row.path }}</el-tag>
</div>
</template> </template>
</span>
</template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -99,7 +86,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize" <ms-table-pagination :change="initApiTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/> :total="total"/>
</el-card> </el-card>
@ -123,7 +110,8 @@
import MsApiCaseList from "./ApiCaseList"; import MsApiCaseList from "./ApiCaseList";
import MsContainer from "../../../common/components/MsContainer"; import MsContainer from "../../../common/components/MsContainer";
import MsBottomContainer from "./BottomContainer"; import MsBottomContainer from "./BottomContainer";
import {Message} from "element-ui"; import ShowMoreBtn from "../../../../components/track/case/components/ShowMoreBtn";
import {API_METHOD_COLOUR} from "../model/JsonData";
export default { export default {
name: "ApiList", name: "ApiList",
@ -136,22 +124,19 @@
MsTag, MsTag,
MsApiCaseList, MsApiCaseList,
MsContainer, MsContainer,
MsBottomContainer MsBottomContainer,
ShowMoreBtn
}, },
data() { data() {
return { return {
result: {},
condition: {}, condition: {},
isHide: true, isHide: true,
selectApi: {}, selectApi: {},
moduleId: "", moduleId: "",
deletePath: "/test/case/delete", deletePath: "/test/case/delete",
methodColorMap: new Map([ selectRows: new Set(),
['GET', "#61AFFE"], ['POST', '#49CC90'], ['PUT', '#fca130'], buttons: [{name: this.$t('api_test.delimit.request.batch_delete'), handleClick: this.handleDeleteBatch}],
['PATCH', '#E2EE11'], ['DELETE', '#f93e3d'], ['OPTIONS', '#0EF5DA'], methodColorMap: new Map(API_METHOD_COLOUR),
['HEAD', '#8E58E7'], ['CONNECT', '#90AFAE'],
['DUBBO', '#C36EEF'], ['SQL', '#0AEAD4'], ['TCP', '#0A52DF'],
]),
tableData: [], tableData: [],
currentPage: 1, currentPage: 1,
pageSize: 10, pageSize: 10,
@ -159,26 +144,24 @@
} }
}, },
props: { props: {
currentProject: { currentProject: Object,
type: Object
},
currentModule: Object currentModule: Object
}, },
created: function () { created: function () {
this.initTableData(); this.initApiTable();
}, },
watch: { watch: {
currentProject() { currentProject() {
this.initTableData(); this.initApiTable();
}, },
currentModule() { currentModule() {
this.initTableData(); this.initApiTable();
} }
}, },
methods: { methods: {
initTableData() { initApiTable() {
if (this.currentModule != null) { if (this.currentModule != null) {
if (this.currentModule.id == "rootID") { if (this.currentModule.id == "root") {
this.condition.moduleIds = []; this.condition.moduleIds = [];
} else { } else {
this.condition.moduleIds = this.currentModule.ids; this.condition.moduleIds = this.currentModule.ids;
@ -192,8 +175,45 @@
this.tableData = response.data.listObject; this.tableData = response.data.listObject;
}); });
}, },
handleSelect(selection, row) {
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) {
this.selectRows.add(selection[0]);
} else {
this.tableData.forEach(item => {
this.$set(item, "showMore", true);
this.selectRows.add(item);
});
}
} else {
this.selectRows.clear();
this.tableData.forEach(row => {
this.$set(row, "showMore", false);
})
}
},
search() { search() {
this.initTableData(); this.initApiTable();
}, },
buildPagePath(path) { buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize; return path + "/" + this.currentPage + "/" + this.pageSize;
@ -202,14 +222,29 @@
editApi(row) { editApi(row) {
this.$emit('editApi', row); this.$emit('editApi', row);
}, },
handleDeleteBatch() {
this.$alert(this.$t('api_test.delimit.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/delimit/deleteBatch/', ids, () => {
this.selectRows.clear();
this.initApiTable();
this.$success(this.$t('commons.delete_success'));
});
}
}
});
},
handleTestCase(testCase) { handleTestCase(testCase) {
this.selectApi = testCase; this.selectApi = testCase;
this.isHide = false; this.isHide = false;
}, },
handleDelete(testCase) { handleDelete(testCase) {
this.$get('/api/delimit/delete/' + testCase.id, () => { this.$get('/api/delimit/delete/' + testCase.id, () => {
Message.success(this.$t('commons.delete_success')); this.$success(this.$t('commons.delete_success'));
this.initTableData(); this.initApiTable();
}); });
}, },
apiCaseClose() { apiCaseClose() {
@ -238,5 +273,4 @@
.api-el-tag { .api-el-tag {
color: white; color: white;
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-loading="result.loading"> <div>
<select-menu <select-menu
:data="projects" :data="projects"
:current-data="currentProject" :current-data="currentProject"
@ -49,7 +49,7 @@
<span class="node-operate child"> <span class="node-operate child">
<el-tooltip <el-tooltip
v-if="data.id!='rootID'" v-if="data.id!='root'"
class="item" class="item"
effect="dark" effect="dark"
:open-delay="200" :open-delay="200"
@ -68,7 +68,7 @@
</el-tooltip> </el-tooltip>
<el-tooltip <el-tooltip
v-if="data.id!='rootID'" v-if="data.id!='root'"
class="item" class="item"
effect="dark" effect="dark"
:open-delay="200" :open-delay="200"
@ -89,6 +89,7 @@
<script> <script>
import MsAddBasisHttpApi from "./basis/AddBasisHttpApi"; import MsAddBasisHttpApi from "./basis/AddBasisHttpApi";
import SelectMenu from "../../../track/common/SelectMenu"; import SelectMenu from "../../../track/common/SelectMenu";
import {OPTIONS, DEFAULT_DATA} from "../model/JsonData";
export default { export default {
name: 'MsApiModule', name: 'MsApiModule',
@ -98,35 +99,15 @@
}, },
data() { data() {
return { return {
options: [{ options: OPTIONS,
value: 'HTTP', value: OPTIONS[0].value,
name: 'HTTP'
}, {
value: 'DUBBO',
name: 'DUBBO'
}, {
value: 'TCP',
name: 'TCP'
}, {
value: 'SQL',
name: 'SQL'
}],
value: 'HTTP',
httpVisible: false, httpVisible: false,
expandedNode: [], expandedNode: [],
result: {},
filterText: "", filterText: "",
nextFlag: true, nextFlag: true,
currentProject: {}, currentProject: {},
projects: [], projects: [],
data: [ data: DEFAULT_DATA,
{
"id": "rootID",
"name": "默认模块",
"level": 0,
"children": [],
}
],
currentModule: {}, currentModule: {},
newLabel: "" newLabel: ""
} }
@ -146,7 +127,7 @@
methods: { methods: {
getApiModuleTree() { getApiModuleTree() {
if (this.currentProject) { if (this.currentProject) {
this.result = this.$get("/api/module/list/" + this.currentProject.id + "/" + this.value, response => { this.$get("/api/module/list/" + this.currentProject.id + "/" + this.value, response => {
if (response.data != undefined && response.data != null) { if (response.data != undefined && response.data != null) {
this.data[0].children = response.data; this.data[0].children = response.data;
let moduleOptions = []; let moduleOptions = [];
@ -220,7 +201,7 @@
param.nodeIds = nodeIds; param.nodeIds = nodeIds;
return param; return param;
}, },
getNodeTree(nodes, id, list) { getTreeNode(nodes, id, list) {
if (!nodes) { if (!nodes) {
return; return;
} }
@ -232,21 +213,21 @@
return; return;
} }
if (nodes[i].children) { if (nodes[i].children) {
this.getNodeTree(nodes[i].children, id, list); this.getTreeNode(nodes[i].children, id, list);
} }
} }
}, },
handleDragEnd(draggingNode, dropNode, dropType, ev) { handleDragEnd(draggingNode, dropNode, dropType, ev) {
if (dropNode.data.id === "rootID" || dropType === "none" || dropType === undefined) { if (dropNode.data.id === "root" || dropType === "none" || dropType === undefined) {
return; return;
} }
let param = this.buildParam(draggingNode, dropNode, dropType); let param = this.buildParam(draggingNode, dropNode, dropType);
this.list = []; this.list = [];
if (param.parentId === "rootID") { if (param.parentId === "root") {
param.parentId = null; param.parentId = null;
} }
this.getNodeTree(this.data, draggingNode.data.id, this.list); this.getTreeNode(this.data, draggingNode.data.id, this.list);
this.$post("/api/module/drag", param, () => { this.$post("/api/module/drag", param, () => {
this.getApiGroupData(); this.getApiGroupData();
@ -256,7 +237,7 @@
}, },
allowDrop(draggingNode, dropNode, type) { allowDrop(draggingNode, dropNode, type) {
if (dropNode.data.id === "rootID") { if (dropNode.data.id === "root") {
return false return false
} else { } else {
return true return true
@ -264,13 +245,12 @@
}, },
allowDrag(draggingNode) { allowDrag(draggingNode) {
// //
if (draggingNode.data.id === "rootID") { if (draggingNode.data.id === "root") {
return false return false
} else { } else {
return true return true
} }
}, },
append(node, data) { append(node, data) {
if (this.nextFlag === true) { if (this.nextFlag === true) {
const newChild = { const newChild = {
@ -311,7 +291,6 @@
this.$set(data, 'isEdit', 1) this.$set(data, 'isEdit', 1)
this.newLabel = data.name this.newLabel = data.name
this.$nextTick(() => { this.$nextTick(() => {
//this.$refs.input.focus();
}) })
}, },
@ -355,7 +334,7 @@
if (data.id === "newId") { if (data.id === "newId") {
url = '/api/module/add'; url = '/api/module/add';
data.level = 1; data.level = 1;
if (node.parent && node.parent.key != "rootID") { if (node.parent && node.parent.key != "root") {
data.parentId = node.parent.key; data.parentId = node.parent.key;
data.level = node.parent.level; data.level = node.parent.level;
} }
@ -377,7 +356,7 @@
}, },
selectModule(data) { selectModule(data) {
if (data.id != "rootID") { if (data.id != "root") {
if (data.path != undefined && !data.path.startsWith("/")) { if (data.path != undefined && !data.path.startsWith("/")) {
data.path = "/" + data.path; data.path = "/" + data.path;
} }

View File

@ -10,7 +10,7 @@
<el-form-item :label="$t('api_report.request')" prop="url"> <el-form-item :label="$t('api_report.request')" prop="url">
<el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="httpForm.url" <el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="httpForm.url"
class="ms-http-input" size="small"> class="ms-http-input" size="small">
<el-select v-model="value" slot="prepend" style="width: 100px" size="small"> <el-select v-model="httpForm.path" slot="prepend" style="width: 100px" size="small">
<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-input> </el-input>
@ -47,8 +47,10 @@
<script> <script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter"; import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {TokenKey, WORKSPACE_ID} from '../../../../../../common/js/constants'; import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import {Scenario, KeyValue, Test} from "../../model/ScenarioModel" import {Test} from "../../model/ScenarioModel"
import {REQ_METHOD} from "../../model/JsonData";
import {getCurrentUser} from "../../../../../../common/js/utils";
export default { export default {
name: "MsAddBasisHttpApi", name: "MsAddBasisHttpApi",
@ -61,7 +63,6 @@
currentModule: {}, currentModule: {},
projectId: "", projectId: "",
maintainerOptions: [], maintainerOptions: [],
test: new Test(),
rule: { rule: {
name: [ name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'}, {required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
@ -70,18 +71,9 @@
url: [{required: true, message: this.$t('api_test.delimit.request.path_info'), trigger: 'blur'}], url: [{required: true, message: this.$t('api_test.delimit.request.path_info'), trigger: 'blur'}],
userId: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}], userId: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
}, },
value: "GET", value: REQ_METHOD[0].id,
options: [{ options: REQ_METHOD,
id: 'GET',
label: 'GET'
}, {
id: 'POST',
label: 'POST'
}],
} }
},
created() {
}, },
methods: { methods: {
saveApi() { saveApi() {
@ -89,26 +81,22 @@
if (valid) { if (valid) {
let bodyFiles = []; let bodyFiles = [];
let url = "/api/delimit/create"; let url = "/api/delimit/create";
this.test.name = this.httpForm.name; let test = new Test();
this.test.path = this.value; this.httpForm.bodyUploadIds = [];
this.test.url = this.httpForm.url; this.httpForm.request = test.request;
this.test.userId = this.httpForm.userId; this.httpForm.request.url = this.httpForm.url;
this.test.description = this.httpForm.description; this.httpForm.request.path = this.httpForm.path;
this.test.request.url = this.httpForm.url; this.httpForm.projectId = this.projectId;
this.test.request.path = this.value; this.httpForm.id = test.id;
this.test.bodyUploadIds = [];
this.test.projectId = this.projectId;
if (this.currentModule != null) { if (this.currentModule != null) {
this.test.modulePath = this.currentModule.path != undefined ? this.currentModule.path : null; this.httpForm.modulePath = this.currentModule.path != undefined ? this.currentModule.path : null;
this.test.moduleId = this.currentModule.id; this.httpForm.moduleId = this.currentModule.id;
} }
let jmx = this.test.toJMX(); let jmx = 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, () => { this.result = this.$fileUpload(url, file, bodyFiles, this.httpForm, () => {
this.httpVisible = false; this.httpVisible = false;
this.$parent.refresh(this.currentModule); this.$parent.refresh(this.currentModule);
}); });
@ -117,36 +105,6 @@
} }
}) })
}, },
urlChange() {
if (!this.httpForm.url) return;
let url = this.getURL(this.addProtocol(this.httpForm.url));
if (url) {
this.httpForm.url = decodeURIComponent(url.origin + url.pathname);
}
}
,
getURL(urlStr) {
try {
let url = new URL(urlStr);
url.searchParams.forEach((value, key) => {
if (key && value) {
this.request.parameters.splice(0, 0, new KeyValue({name: key, value: value}));
}
});
return url;
} catch (e) {
this.$error(this.$t('api_test.request.url_invalid'), 2000);
}
}
,
addProtocol(url) {
if (url) {
if (!url.toLowerCase().startsWith("https") && !url.toLowerCase().startsWith("http")) {
return "https://" + url;
}
}
return url;
},
getMaintainerOptions() { getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID); let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => { this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
@ -155,8 +113,7 @@
} }
, ,
open(currentModule, projectId) { open(currentModule, projectId) {
this.httpForm = {}; this.httpForm = {path: REQ_METHOD[0].id, userId: getCurrentUser().id};
this.test = new Test();
this.currentModule = currentModule; this.currentModule = currentModule;
this.projectId = projectId; this.projectId = projectId;
this.getMaintainerOptions(); this.getMaintainerOptions();

View File

@ -6,7 +6,7 @@
<el-form :model="httpForm" :rules="rule" ref="httpForm" :inline="true" label-position="right"> <el-form :model="httpForm" :rules="rule" ref="httpForm" :inline="true" label-position="right">
<div style="float: right;margin-right: 20px"> <div style="float: right;margin-right: 20px">
<el-button type="primary" size="small" @click="saveApi">{{$t('commons.save')}}</el-button> <el-button type="primary" size="small" @click="saveApi">{{$t('commons.save')}}</el-button>
<el-button type="primary" size="small" @click="runTest(httpForm)">{{$t('commons.test')}}</el-button> <el-button type="primary" size="small" @click="runTest">{{$t('commons.test')}}</el-button>
</div> </div>
<br/> <br/>
<div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div> <div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div>
@ -60,13 +60,13 @@
</el-form-item> </el-form-item>
<div style="font-size: 16px;color: #333333;padding-top: 30px">请求参数</div> <div style="font-size: 16px;color: #333333;padding-top: 30px">{{$t('api_test.delimit.request.req_param')}}</div>
<br/> <br/>
<!-- HTTP 请求参数 --> <!-- HTTP 请求参数 -->
<ms-api-request-form :debug-report-id="debugReportId" :request="this.test.request"/> <ms-api-request-form :request="test.request"/>
</el-form> </el-form>
<div style="font-size: 16px;color: #333333 ;padding-top: 30px">响应模版</div> <div style="font-size: 16px;color: #333333 ;padding-top: 30px">{{$t('api_test.delimit.request.res_param')}}</div>
<br/> <br/>
<ms-response-text :response="responseData"></ms-response-text> <ms-response-text :response="responseData"></ms-response-text>
</el-card> </el-card>
@ -77,7 +77,7 @@
import MsApiRequestForm from "../request/ApiRequestForm"; import MsApiRequestForm from "../request/ApiRequestForm";
import MsResponseText from "../../../report/components/ResponseText"; import MsResponseText from "../../../report/components/ResponseText";
import {WORKSPACE_ID} from '../../../../../../common/js/constants'; import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import {Scenario, KeyValue, Test} from "../../model/ScenarioModel" import {REQ_METHOD, API_STATUS} from "../../model/JsonData";
export default { export default {
name: "MsAddCompleteHttpApi", name: "MsAddCompleteHttpApi",
@ -95,37 +95,28 @@
status: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}], status: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}],
}, },
httpForm: {}, httpForm: {},
reqValue: '',
debugReportId: '',
maintainerOptions: [], maintainerOptions: [],
responseData: {}, responseData: {},
currentScenario: new Scenario(),
currentModule: {}, currentModule: {},
postUrl: "", reqOptions: REQ_METHOD,
reqOptions: [{ options: API_STATUS
id: 'GET',
label: 'GET'
}, {
id: 'POST',
label: 'POST'
}],
options: [{
id: 'Prepare',
label: '未开始'
}, {
id: 'Underway',
label: '进行中'
}, {
id: 'Completed',
label: '已完成'
}],
} }
}, },
props: {httpData: {}, moduleOptions: {}, currentProject: {}, test: {}}, props: {httpData: {}, moduleOptions: {}, currentProject: {}, test: {}},
methods: { methods: {
runTest(data) { runTest() {
this.$emit('runTest', data); if (this.currentProject === null) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs['httpForm'].validate((valid) => {
if (valid) {
this.setParameter();
this.$emit('runTest', this.httpForm);
} else {
return false;
}
})
}, },
getMaintainerOptions() { getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID); let workspaceId = localStorage.getItem(WORKSPACE_ID);
@ -133,35 +124,26 @@
this.maintainerOptions = response.data; this.maintainerOptions = response.data;
}); });
}, },
setParameter() {
this.httpForm.test = this.test;
this.httpForm.test.request.url = this.httpForm.url;
this.httpForm.test.request.path = this.httpForm.path;
this.httpForm.modulePath = this.getPath(this.httpForm.moduleId);
},
saveApi() { saveApi() {
if (this.currentProject === null) { if (this.currentProject === null) {
this.$error(this.$t('api_test.select_project'), 2000); this.$error(this.$t('api_test.select_project'), 2000);
return; return;
} }
this.$refs['httpForm'].validate((valid) => { this.$refs['httpForm'].validate((valid) => {
if (valid) { if (valid) {
this.test.name = this.httpForm.name; this.setParameter();
this.test.path = this.httpForm.path; this.$emit('saveApi', this.httpForm);
this.test.url = this.httpForm.url;
this.test.userId = this.httpForm.userId;
this.test.description = this.httpForm.description;
this.test.status = this.httpForm.status;
this.test.moduleId = this.httpForm.moduleId;
this.test.projectId = this.currentProject.id;
this.test.modulePath = this.getPath(this.httpForm.moduleId);
this.test.bodyUploadIds = [];
this.test.request.url = this.httpForm.url;
this.test.request.path = this.httpForm.path;
console.log(this.httpForm)
this.$emit('saveApi', this.test);
}
else {
return false;
}
} }
) else {
return false;
}
})
}, },
getPath(id) { getPath(id) {
if (id === null) { if (id === null) {

View File

@ -1,22 +1,20 @@
<template> <template>
<div class="card-container"> <div class="card-container">
<el-card class="card-content" v-loading="result.loading"> <el-card class="card-content">
<el-form :model="httpForm" :rules="rules" ref="httpForm" :inline="true" label-position="right">
<el-form :model="httpForm" :rules="rule" ref="httpForm" :inline="true" :label-position="labelPosition">
<div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div> <div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div>
<br/> <br/>
<el-form-item :label="$t('api_report.request')" prop="responsible"> <el-form-item :label="$t('api_report.request')" prop="responsible">
<el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="httpForm.url"
<el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="httpForm.request"
class="ms-http-input" size="small"> class="ms-http-input" size="small">
<el-select v-model="reqValue" slot="prepend" style="width: 100px" size="small"> <el-select v-model="httpForm.path" slot="prepend" style="width: 100px" size="small">
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/> <el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select> </el-select>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-dropdown split-button type="primary" class="ms-api-buttion" @click="handleCommand" <el-dropdown split-button type="primary" class="ms-api-buttion" @click="handleCommand"
@command="handleCommand" size="small"> @command="handleCommand" size="small">
@ -27,13 +25,13 @@
</el-dropdown> </el-dropdown>
</el-form-item> </el-form-item>
<div style="font-size: 16px;color: #333333;padding-top: 30px">请求参数</div> <div style="font-size: 16px;color: #333333;padding-top: 30px">{{$t('api_test.delimit.request.req_param')}}</div>
<br/> <br/>
<!-- HTTP 请求参数 --> <!-- HTTP 请求参数 -->
<ms-api-request-form :debug-report-id="debugReportId" :request="selected" :scenario="currentScenario"/> <ms-api-request-form :request="test.request"/>
</el-form> </el-form>
<div style="font-size: 16px;color: #333333 ;padding-top: 30px">响应内容</div> <div style="font-size: 16px;color: #333333 ;padding-top: 30px">{{$t('api_test.delimit.request.res_param')}}</div>
<br/> <br/>
<ms-response-text :response="responseData"></ms-response-text> <ms-response-text :response="responseData"></ms-response-text>
</el-card> </el-card>
@ -42,35 +40,26 @@
<script> <script>
import MsApiRequestForm from "../request/ApiRequestForm"; import MsApiRequestForm from "../request/ApiRequestForm";
import {Request, Scenario} from "../../model/ScenarioModel"; import {Test} from "../../model/ScenarioModel";
import MsResponseText from "../../../report/components/ResponseText"; import MsResponseText from "../../../report/components/ResponseText";
import {REQ_METHOD} from "../../model/JsonData";
export default { export default {
name: "ApiConfig", name: "ApiConfig",
components: {MsResponseText, MsApiRequestForm}, components: {MsResponseText, MsApiRequestForm},
data() { data() {
return { return {
result: {}, rules: {
rule: {}, path: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
labelPosition: 'right', url: [{required: true, message: this.$t('api_test.delimit.request.path_info'), trigger: 'blur'}],
httpForm: {}, },
httpForm: {path: REQ_METHOD[0].id},
options: [], options: [],
reqValue: '',
debugReportId: '',
responseData: {}, responseData: {},
currentScenario: Scenario, test: new Test(),
selected: [Scenario, Request], reqOptions: REQ_METHOD,
reqOptions: [{
id: 'GET',
label: 'GET'
}, {
id: 'POST',
label: 'POST'
}],
moduleValue: '',
} }
}, },
props: {httpData: {},},
methods: { methods: {
handleCommand(e) { handleCommand(e) {
if (e === "save_as") { if (e === "save_as") {
@ -78,12 +67,15 @@
} }
}, },
saveAs() { saveAs() {
this.$emit('saveAs', this.httpForm); this.$refs['httpForm'].validate((valid) => {
} if (valid) {
}, this.httpForm.request = JSON.stringify(this.test.request);
watch: { this.$emit('saveAs', this.httpForm);
httpData(v) { }
this.httpForm = v; else {
return false;
}
})
} }
} }
} }
@ -92,6 +84,6 @@
<style scoped> <style scoped>
.ms-http-input { .ms-http-input {
width: 500px; width: 500px;
margin-top:5px; margin-top: 5px;
} }
</style> </style>

View File

@ -1,17 +1,17 @@
<template> <template>
<div class="card-container"> <div class="card-container">
<el-card class="card-content" v-loading="result.loading"> <el-card class="card-content">
<el-form :model="httpForm" :rules="rule" ref="httpForm" :inline="true" :label-position="labelPosition"> <el-form :model="apiData" ref="apiData" :inline="true" label-position="right">
<div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div> <div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div>
<br/> <br/>
<el-form-item :label="$t('api_report.request')" prop="responsible"> <el-form-item :label="$t('api_report.request')" prop="responsible">
<el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="httpForm.request" <el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="url"
class="ms-http-input" size="small"> class="ms-http-input" size="small" :disabled="false">
<el-select v-model="reqValue" slot="prepend" style="width: 100px"> <el-select v-model="path" slot="prepend" style="width: 100px">
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/> <el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select> </el-select>
</el-input> </el-input>
@ -33,13 +33,13 @@
</el-form-item> </el-form-item>
<div style="font-size: 16px;color: #333333;padding-top: 30px">请求参数</div> <div style="font-size: 16px;color: #333333;padding-top: 30px">{{$t('api_test.delimit.request.req_param')}}</div>
<br/> <br/>
<!-- HTTP 请求参数 --> <!-- HTTP 请求参数 -->
<ms-api-request-form :debug-report-id="debugReportId" :request="selected" :scenario="currentScenario"/> <ms-api-request-form :request="apiData.request"/>
</el-form> </el-form>
<div style="font-size: 16px;color: #333333 ;padding-top: 30px">响应内容</div> <div style="font-size: 16px;color: #333333 ;padding-top: 30px">{{$t('api_test.delimit.request.res_param')}}</div>
<br/> <br/>
<ms-response-text :response="responseData"></ms-response-text> <ms-response-text :response="responseData"></ms-response-text>
@ -47,7 +47,7 @@
<!-- 加载用例 --> <!-- 加载用例 -->
<ms-bottom-container v-bind:enableAsideHidden="isHide"> <ms-bottom-container v-bind:enableAsideHidden="isHide">
<ms-api-case-list @apiCaseClose="apiCaseClose" :api="httpForm"></ms-api-case-list> <ms-api-case-list @apiCaseClose="apiCaseClose" @selectTestCase="selectTestCase" :api="apiData" ref="caseList"/>
</ms-bottom-container> </ms-bottom-container>
</div> </div>
@ -55,69 +55,121 @@
<script> <script>
import MsApiRequestForm from "../request/ApiRequestForm"; import MsApiRequestForm from "../request/ApiRequestForm";
import {Request, Scenario} from "../../model/ScenarioModel"; import {downloadFile, getUUID} from "@/common/js/utils";
import MsResponseText from "../../../report/components/ResponseText"; import MsResponseText from "../../../report/components/ResponseText";
import MsApiCaseList from "../ApiCaseList"; import MsApiCaseList from "../ApiCaseList";
import MsContainer from "../../../../common/components/MsContainer"; import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer"; import MsBottomContainer from "../BottomContainer";
import {RequestFactory, Test} from "../../model/ScenarioModel";
import {REQ_METHOD} from "../../model/JsonData";
export default { export default {
name: "ApiConfig", name: "ApiConfig",
components: {MsResponseText, MsApiRequestForm, MsApiCaseList, MsContainer, MsBottomContainer}, components: {MsResponseText, MsApiRequestForm, MsApiCaseList, MsContainer, MsBottomContainer},
data() { data() {
return { return {
result: {},
rule: {},
isHide: true, isHide: true,
labelPosition: 'right', url: '',
httpForm: {}, path: '',
options: [], currentRequest: {},
reqValue: '',
debugReportId: '',
responseData: {}, responseData: {},
currentScenario: Scenario, reqOptions: REQ_METHOD,
selected: [Scenario, Request],
reqOptions: [{
id: 'GET',
label: 'GET'
}, {
id: 'POST',
label: 'POST'
}],
moduleValue: '',
} }
}, },
props: {httpData: {},}, props: {apiData: {}},
methods: { methods: {
handleCommand(e) { handleCommand(e) {
switch (e) { switch (e) {
case "load_case": case "load_case":
return this.loadCase(); return this.loadCase();
case "save_as_case": case "save_as_case":
return ""; return this.saveAsCase();
case "update_api": case "update_api":
return "update_api"; return this.updateApi();
case "save_as_api": case "save_as_api":
return "save_as_api"; return this.saveAsApi();
default: default:
return []; return [];
} }
}, },
saveAs() { saveAs() {
this.$emit('saveAs', this.httpForm); this.$emit('saveAs', this.apiData);
}, },
loadCase() { loadCase() {
console.log(this.httpForm)
this.isHide = false; this.isHide = false;
}, },
apiCaseClose() { apiCaseClose() {
this.isHide = true; this.isHide = true;
},
getBodyUploadFiles() {
let bodyUploadFiles = [];
this.apiData.bodyUploadIds = [];
let request = this.apiData.request;
if (request.body) {
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;
this.apiData.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
}
return bodyUploadFiles;
},
saveAsCase() {
let testCase = {};
let test = new Test();
test.request = this.apiData.request;
testCase.test = test;
testCase.request = this.apiData.request;
testCase.name = this.apiData.name;
testCase.priority = "P0";
this.$refs.caseList.saveTestCase(testCase);
},
saveAsApi() {
let data = {};
data.request = JSON.stringify(this.apiData.request);
data.path = this.apiData.path;
data.url = this.apiData.url;
data.status = this.apiData.status;
data.userId = this.apiData.userId;
data.description = this.apiData.description;
this.$emit('saveAsApi', data);
},
editApi(url) {
this.apiData.url = this.url;
this.apiData.path = this.path;
let bodyFiles = this.getBodyUploadFiles();
let jmx = this.apiData.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
this.$fileUpload(url, file, bodyFiles, this.apiData, () => {
this.$success(this.$t('commons.save_success'));
this.$emit('saveApi', this.apiData);
});
},
updateApi() {
let url = "/api/delimit/update";
this.editApi(url);
},
selectTestCase(item) {
if (item != null) {
this.apiData.request = new RequestFactory(JSON.parse(item.request));
} else {
this.apiData.request = this.currentRequest;
}
} }
}, },
watch: { created() {
httpData(v) { this.currentRequest = this.apiData.request;
this.httpForm = v; this.url = this.apiData.url;
} this.path = this.apiData.path;
} }
} }
</script> </script>

View File

@ -0,0 +1,39 @@
/* 用例等级 */
export const PRIORITY = [
{name: 'P0', id: 'P0'},
{name: 'P1', id: 'P1'},
{name: 'P2', id: 'P2'},
{name: 'P3', id: 'P3'}
]
export const OPTIONS = [
{value: 'HTTP', name: 'HTTP'},
{value: 'DUBBO', name: 'DUBBO'},
{value: 'TCP', name: 'TCP'},
{value: 'SQL', name: 'SQL'}
]
export const DEFAULT_DATA = [{
"id": "root",
"name": "默认模块",
"level": 0,
"children": [],
}]
export const REQ_METHOD = [
{id: 'GET', label: 'GET'},
{id: 'POST', label: 'POST'}
]
export const API_STATUS = [
{id: 'Prepare', label: '未开始'},
{id: 'Underway', label: '进行中'},
{id: 'Completed', label: '已完成'}
]
export const API_METHOD_COLOUR = [
['GET', "#61AFFE"], ['POST', '#49CC90'], ['PUT', '#fca130'],
['PATCH', '#E2EE11'], ['DELETE', '#f93e3d'], ['OPTIONS', '#0EF5DA'],
['HEAD', '#8E58E7'], ['CONNECT', '#90AFAE'],
['DUBBO', '#C36EEF'], ['SQL', '#0AEAD4'], ['TCP', '#0A52DF'],
]

View File

@ -498,6 +498,10 @@ export default {
verification_method: "Verification method", verification_method: "Verification method",
verified: "verified", verified: "verified",
encryption: "encryption", encryption: "encryption",
req_param: "Request parameter",
res_param: "Response template",
batch_delete: "Batch deletion",
delete_confirm: "confirm deletion",
} }
}, },
environment: { environment: {

View File

@ -499,7 +499,10 @@ export default {
verification_method: "认证方式", verification_method: "认证方式",
verified: "认证", verified: "认证",
encryption: "加密", encryption: "加密",
req_param: "请求参数",
res_param: "响应模版",
batch_delete: "批量删除",
delete_confirm: "确认删除接口",
} }
}, },
environment: { environment: {

View File

@ -499,8 +499,11 @@ export default {
verification_method: "認證方式", verification_method: "認證方式",
verified: "認證", verified: "認證",
encryption: "加密", encryption: "加密",
req_param: "請求參數",
res_param: "響應模版",
batch_delete: "批量删除",
delete_confirm: "確認刪除接口",
} }
}, },
environment: { environment: {
name: "環境名稱", name: "環境名稱",