api界面,未完待续

This commit is contained in:
q4speed 2020-04-17 18:38:38 +08:00
parent 30f6745b90
commit f7a11e9405
13 changed files with 591 additions and 118 deletions

View File

@ -26,13 +26,14 @@
</ms-api-collapse-item>
</ms-api-collapse>
</div>
<el-button class="scenario-create" type="primary" size="mini" icon="el-icon-plus" plain @click="create"/>
<el-button class="scenario-create" type="primary" size="mini" icon="el-icon-plus" plain
@click="createScenario"/>
</el-aside>
<el-main class="scenario-main">
<div class="scenario-form">
<ms-api-scenario-form :scenario="selected"></ms-api-scenario-form>
<ms-api-request-form :request="selected"></ms-api-request-form>
<ms-api-scenario-form :scenario="selected" v-if="isScenario"></ms-api-scenario-form>
<ms-api-request-form :request="selected" v-if="isRequest"></ms-api-request-form>
</div>
</el-main>
</el-container>
@ -49,6 +50,7 @@
import MsApiRequest from "./components/ApiRequest";
import MsApiRequestForm from "./components/ApiRequestForm";
import MsApiScenarioForm from "./components/ApiScenarioForm";
import {Scenario, Request} from "./model/APIModel";
export default {
name: "MsApiScenarioConfig",
@ -64,6 +66,17 @@
},
methods: {
createScenario: function () {
let scenario = new Scenario({name: "Scenario"});
this.scenarios.push(scenario);
},
deleteScenario: function (index) {
this.scenarios.splice(index, 1);
if (this.scenarios.length === 0) {
this.createScenario();
this.select(this.scenarios[0]);
}
},
handleChange: function (index) {
this.select(this.scenarios[index]);
},
@ -74,35 +87,23 @@
break;
}
},
createScenario: function () {
return {
type: "Scenario",
name: "Scenario",
address: "",
file: "",
variables: [],
headers: [],
requests: []
}
},
deleteScenario: function (index) {
this.scenarios.splice(index, 1);
if (this.scenarios.length === 0) {
this.create();
}
},
create: function () {
let scenario = this.createScenario();
this.scenarios.push(scenario);
},
select: function (obj) {
this.selected = obj;
}
},
computed: {
isScenario() {
return this.selected instanceof Scenario;
},
isRequest() {
return this.selected instanceof Request;
}
},
created() {
if (this.scenarios.length === 0) {
this.create();
this.createScenario();
this.select(this.scenarios[0]);
}
}
@ -137,6 +138,9 @@
}
.scenario-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
width: 100%;
}

View File

@ -0,0 +1,64 @@
<template>
<div>
<el-row :gutter="10" type="flex" align="middle">
<el-col :span="4">
<el-select class="assertion-item" v-model="regex.subject" size="small"
:placeholder="$t('api_test.request.assertions.select_subject')">
<el-option label="Http-Code" value="HTTP-CODE"></el-option>
<el-option label="Header" value="HEADER"></el-option>
<el-option label="Body" value="BODY"></el-option>
</el-select>
</el-col>
<el-col :span="19">
<el-input v-model="regex.expression" maxlength="255" size="small" show-word-limit
:placeholder="$t('api_test.request.assertions.expression')"/>
</el-col>
<el-col :span="1" class="assertion-btn">
<el-button type="danger" size="mini" icon="el-icon-delete" circle @click="remove" v-if="edit"/>
<el-button type="primary" size="small" icon="el-icon-plus" plain @click="add" v-else/>
</el-col>
</el-row>
</div>
</template>
<script>
import {Regex} from "../model/APIModel";
export default {
name: "MsApiAssertionRegex",
props: {
regex: {
type: Regex,
default: () => {
return new Regex();
}
},
edit: {
type: Boolean,
default: false
},
index: Number,
list: Array
},
methods: {
add: function () {
this.list.push(new Regex(this.regex));
},
remove: function () {
this.list.splice(this.index, 1);
}
}
}
</script>
<style scoped>
.assertion-item {
width: 100%;
}
.assertion-btn {
text-align: center;
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<div>
<el-row :gutter="10" align="middle">
<el-col :span="23">
<el-input v-model="time" step="100" size="small" type="number"
:placeholder="$t('api_test.request.assertions.response_in_time')"/>
</el-col>
<el-col :span="1" class="assertion-btn">
<el-button type="danger" size="mini" icon="el-icon-delete" circle @click="remove" v-if="edit"/>
<el-button type="primary" size="small" icon="el-icon-plus" plain @click="add" v-else/>
</el-col>
</el-row>
</div>
</template>
<script>
import {ResponseTime} from "../model/APIModel";
export default {
name: "MsApiAssertionResponseTime",
props: {
edit: Boolean,
responseTime: ResponseTime
},
data() {
return {
time: this.responseTime.responseInTime
}
},
methods: {
add: function () {
this.remove();
setTimeout(() => {
this.responseTime.responseInTime = this.time;
})
},
remove: function () {
this.responseTime.responseInTime = null;
}
}
}
</script>
<style scoped>
.assertion-btn {
text-align: center;
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<div>
<el-row :gutter="10">
<el-col :span="4">
<el-select class="assertion-item" v-model="subject" size="small"
:placeholder="$t('api_test.request.assertions.select_subject')">
<el-option label="Http-Code" value="HTTP-CODE"></el-option>
<el-option label="Header" value="HEADER"></el-option>
<el-option label="Body" value="BODY"></el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-select class="assertion-item" v-model="condition" size="small"
:placeholder="$t('api_test.request.assertions.select_contains')">
<el-option :label="$t('api_test.request.assertions.contains')" value="CONTAINS"></el-option>
<el-option :label="$t('api_test.request.assertions.not_contains')" value="NOT_CONTAINS"></el-option>
<el-option :label="$t('api_test.request.assertions.equals')" value="EQUALS"></el-option>
<el-option :label="$t('api_test.request.assertions.start_with')" value="START_WITH"></el-option>
<el-option :label="$t('api_test.request.assertions.end_with')" value="END_WITH"></el-option>
</el-select>
</el-col>
<el-col :span="15">
<el-input v-model="value" maxlength="255" size="small" show-word-limit
:placeholder="$t('api_test.request.assertions.value')"/>
</el-col>
<el-col :span="1">
<el-button type="primary" size="small" icon="el-icon-plus" plain @click="add"/>
</el-col>
</el-row>
</div>
</template>
<script>
import {Regex} from "../model/APIModel";
export default {
name: "MsApiAssertionText",
props: {
list: Array
},
data() {
return {
subject: "",
condition: "",
value: ""
}
},
methods: {
add: function () {
this.list.push(this.toRegex());
},
toRegex: function () {
let expression = "";
let description = "";
switch (this.condition) {
case "CONTAINS":
expression = ".*" + this.value + ".*";
description = "contains: " + this.value;
break;
case "NOT_CONTAINS":
expression = "^((?!" + this.value + ").)*$";
description = "not contains: " + this.value;
break;
case "EQUALS":
expression = "^" + this.value + "$";
description = "equals: " + this.value;
break;
case "START_WITH":
expression = "^" + this.value;
description = "start with: " + this.value;
break;
case "END_WITH":
expression = this.value + "$";
description = "end with: " + this.value;
break;
}
return new Regex({
subject: this.subject,
expression: expression,
description: description
}
);
}
}
}
</script>
<style scoped>
.assertion-item {
width: 100%;
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div>
<el-row :gutter="10">
<el-col :span="4">
<el-select class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')"
size="small">
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
<el-option :label="$t('api_test.request.assertions.response_time')" :value="options.RESPONSE_TIME"/>
</el-select>
</el-col>
<el-col :span="20">
<ms-api-assertion-text :list="assertions.regex" v-if="type === options.TEXT"/>
<ms-api-assertion-regex :list="assertions.regex" v-if="type === options.REGEX"/>
<ms-api-assertion-response-time :response-time="assertions.responseTime" v-if="type === options.RESPONSE_TIME"/>
</el-col>
</el-row>
<ms-api-assertions-edit :assertions="assertions"/>
</div>
</template>
<script>
import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionResponseTime from "./ApiAssertionResponseTime";
import {ASSERTION_TYPE, Assertions, Regex} from "../model/APIModel";
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
export default {
name: "MsApiAssertions",
components: {MsApiAssertionsEdit, MsApiAssertionResponseTime, MsApiAssertionRegex, MsApiAssertionText},
props: {
assertions: Assertions
},
data() {
return {
options: ASSERTION_TYPE,
type: "",
}
},
methods: {
createRegex: function () {
return new Regex();
}
}
}
</script>
<style scoped>
.assertion-item {
width: 100%;
}
</style>

View File

@ -0,0 +1,57 @@
<template>
<div>
<div class="assertion-item-editing regex" v-if="assertions.regex.length > 0">
<div>
{{$t("api_test.request.assertions.regex")}}
</div>
<div class="regex-item" v-for="(regex, index) in assertions.regex" :key="index">
<ms-api-assertion-regex :list="assertions.regex" :regex="regex" :edit="true" :index="index"/>
</div>
</div>
<div class="assertion-item-editing response-time" v-if="assertions.responseTime.isValid()">
<div>
{{$t("api_test.request.assertions.response_time")}}
</div>
<ms-api-assertion-response-time :response-time="assertions.responseTime" :edit="true"/>
</div>
</div>
</template>
<script>
import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionResponseTime from "./ApiAssertionResponseTime";
import {Assertions} from "../model/APIModel";
export default {
name: "MsApiAssertionsEdit",
components: {MsApiAssertionResponseTime, MsApiAssertionRegex},
props: {
assertions: Assertions
}
}
</script>
<style scoped>
.assertion-item-editing {
padding-left: 10px;
margin-top: 10px;
}
.assertion-item-editing.regex {
border-left: 2px solid #7B0274;
}
.assertion-item-editing.response-time {
border-left: 2px solid #DD0240;
}
.regex-item {
margin-top: 10px;
}
</style>

View File

@ -1,15 +1,15 @@
<template>
<div>
<el-radio-group v-model="body.type" size="mini">
<el-radio-button label="kv">
<el-radio-button :label="type.KV">
{{$t('api_test.request.body_kv')}}
</el-radio-button>
<el-radio-button label="text">
<el-radio-button :label="type.TEXT">
{{$t('api_test.request.body_text')}}
</el-radio-button>
</el-radio-group>
<ms-api-key-value :items="body.kvs" v-if="isKV"/>
<ms-api-key-value :items="body.kvs" v-if="body.isKV()"/>
<el-input class="textarea" type="textarea" v-model="body.text" :autosize="{ minRows: 10, maxRows: 25}" resize="none"
v-else/>
@ -18,23 +18,26 @@
<script>
import MsApiKeyValue from "./ApiKeyValue";
import {Body, BODY_TYPE} from "../model/APIModel";
export default {
name: "MsApiBody",
components: {MsApiKeyValue},
props: {
body: Object
body: Body
},
data() {
return {};
return {
type: BODY_TYPE
};
},
methods: {},
computed: {
isKV() {
return this.body.type === "kv";
created() {
if (this.body.type === null) {
this.body.type = BODY_TYPE.KV;
}
}
}

View File

@ -12,7 +12,8 @@
<el-input v-model="item.value" placeholder="Value" size="small" maxlength="100" @change="check"/>
</el-col>
<el-col :span="1">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"/>
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index)"/>
</el-col>
</el-row>
</div>
@ -20,6 +21,8 @@
</template>
<script>
import {KeyValue} from "../model/APIModel";
export default {
name: "MsApiKeyValue",
@ -29,23 +32,20 @@
},
methods: {
create: function () {
return {
key: "",
value: ""
}
add: function () {
this.items.push(new KeyValue());
},
remove: function (index) {
this.items.splice(index, 1);
if (this.items.length === 0) {
this.items.push(this.create());
this.add();
}
},
check: function () {
let isNeedCreate = true;
let removeIndex = -1;
this.items.forEach((item, index) => {
if (item.key === "" && item.value === "") {
if (!item.key && !item.value) {
//
if (index !== this.items.length - 1) {
removeIndex = index;
@ -55,18 +55,21 @@
}
});
if (isNeedCreate) {
this.items.push(this.create());
this.add();
}
if (removeIndex !== -1) {
this.remove(removeIndex);
}
// TODO key
},
isDisable: function (index) {
return this.items.length - 1 === index;
}
},
created() {
if (this.items.length === 0) {
this.items.push(this.create());
this.add();
}
}
}

View File

@ -2,13 +2,14 @@
<div class="request-container">
<div class="request-item" v-for="(request, index) in requests" :key="index" @click="select(request)"
:class="{'selected': isSelected(request)}">
<span class="request-method">
<el-row type="flex">
<div class="request-method">
{{request.method}}
</span>
<span class="request-name">
</div>
<div class="request-name">
{{request.name}}
</span>
<span class="request-btn">
</div>
<div class="request-btn">
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link el-icon-more"></span>
<el-dropdown-menu slot="dropdown">
@ -16,14 +17,15 @@
<el-dropdown-item :command="{type: 'delete', index: index}">删除请求</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</div>
<el-button class="request-create" type="primary" size="mini" icon="el-icon-plus" plain @click="create"/>
</el-row>
</div>
<el-button class="request-create" type="primary" size="mini" icon="el-icon-plus" plain @click="createRequest"/>
</div>
</template>
<script>
import {generateId} from 'element-ui/src/utils/util';
import {Request} from "../model/APIModel";
export default {
name: "MsApiRequest",
@ -34,7 +36,7 @@
data() {
return {
selected: 0
selected: 0,
}
},
@ -47,10 +49,20 @@
},
methods: {
create: function () {
let request = this.createRequest();
createRequest: function () {
let request = new Request({method: "GET"});
this.requests.push(request);
},
copyRequest: function (index) {
let request = this.requests[index];
this.requests.push(JSON.parse(JSON.stringify(request)));
},
deleteRequest: function (index) {
this.requests.splice(index, 1);
if (this.requests.length === 0) {
this.createRequest();
}
},
handleCommand: function (command) {
switch (command.type) {
case "copy":
@ -61,33 +73,6 @@
break;
}
},
copyRequest: function (index) {
let request = this.requests[index];
this.requests.push(JSON.parse(JSON.stringify(request)));
},
deleteRequest: function (index) {
this.requests.splice(index, 1);
if (this.requests.length === 0) {
this.create();
}
},
createRequest: function () {
return {
randomId: generateId(),
type: "Request",
method: "GET",
name: "",
parameters: [],
headers: [],
body: {
type: "kv",
kvs: [],
text: ""
},
assertions: [],
extract: []
}
},
select: function (request) {
this.selected = request;
this.open(request);
@ -96,7 +81,7 @@
created() {
if (this.requests.length === 0) {
this.create();
this.createRequest();
this.select(this.requests[0]);
}
}
@ -126,11 +111,13 @@
.request-method {
padding: 0 5px;
width: 60px;
color: #1E90FF;
}
.request-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
width: 100%;
}

View File

@ -1,11 +1,11 @@
<template>
<el-form :model="request" :rules="rules" ref="request" label-width="100px" label-position="left" v-if="isRequest">
<el-form :model="request" :rules="rules" ref="request" label-width="100px">
<el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input v-model="request.name"></el-input>
<el-input v-model="request.name" maxlength="100"></el-input>
</el-form-item>
<el-form-item :label="$t('api_test.request.url')" prop="url">
<el-input v-model="request.url" :placeholder="$t('api_test.request.url_describe')">
<el-input v-model="request.url" maxlength="100" :placeholder="$t('api_test.request.url_description')">
<el-select v-model="request.method" slot="prepend" class="request-method-select">
<el-option label="GET" value="GET"></el-option>
<el-option label="POST" value="POST"></el-option>
@ -29,8 +29,8 @@
<el-tab-pane :label="$t('api_test.request.body')" name="body" v-if="isNotGet">
<ms-api-body :body="request.body"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.assertions')" name="assertions" v-if="false">
TODO
<el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions">
<ms-api-assertions :assertions="request.assertions"></ms-api-assertions>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.extract')" name="extract" v-if="false">
TODO
@ -42,10 +42,11 @@
<script>
import MsApiKeyValue from "./ApiKeyValue";
import MsApiBody from "./ApiBody";
import MsApiAssertions from "./ApiAssertions";
export default {
name: "MsApiRequestForm",
components: {MsApiBody, MsApiKeyValue},
components: {MsApiAssertions, MsApiBody, MsApiKeyValue},
props: {
request: Object
},
@ -53,14 +54,19 @@
data() {
return {
activeName: "parameters",
rules: {}
rules: {
name: [
{required: true, message: this.$t('api_test.scenario.input_name'), trigger: 'blur'},
{max: 100, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'}
],
url: [
{max: 100, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'}
]
}
}
},
computed: {
isRequest() {
return this.request.type === "Request";
},
isNotGet() {
return this.request.method !== "GET";
}

View File

@ -1,11 +1,11 @@
<template>
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px" label-position="left" v-if="isScenario">
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px">
<el-form-item :label="$t('api_test.scenario.name')" prop="name">
<el-input v-model="scenario.name"></el-input>
<el-input v-model="scenario.name" maxlength="100"></el-input>
</el-form-item>
<el-form-item :label="$t('api_test.scenario.base_url')" prop="url">
<el-input :placeholder="$t('api_test.scenario.base_url_describe')" v-model="scenario.url"></el-input>
<el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="100"/>
</el-form-item>
<el-tabs v-model="activeName">
@ -32,15 +32,17 @@
data() {
return {
activeName: "variables",
rules: {}
rules: {
name: [
{required: true, message: this.$t('api_test.scenario.input_name'), trigger: 'blur'},
{max: 100, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'}
],
url: [
{max: 100, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'}
]
}
}
},
computed: {
isScenario() {
return this.scenario.type === "Scenario";
}
},
}
</script>

View File

@ -0,0 +1,123 @@
import {generateId} from "element-ui/src/utils/util";
const assign = function (obj, options) {
for (let name in options) {
if (options.hasOwnProperty(name)) {
obj[name] = options[name];
}
}
}
export const BODY_TYPE = {
KV: "KV",
TEXT: "TEXT"
}
export const ASSERTION_TYPE = {
TEXT: "TEXT",
REGEX: "REGEX",
RESPONSE_TIME: "RESPONSE_TIME"
}
export class Scenario {
constructor(options) {
this.name = null;
this.url = null;
this.variables = [];
this.headers = [];
this.requests = [];
assign(this, options);
}
}
export class Request {
constructor(options) {
this.randomId = generateId();
this.name = null;
this.url = null;
this.method = null;
this.parameters = [];
this.headers = [];
this.body = new Body();
this.assertions = new Assertions();
this.extract = [];
assign(this, options);
}
}
export class Body {
constructor(options) {
this.type = null;
this.text = null;
this.kvs = [];
assign(this, options);
}
isKV() {
return this.type === BODY_TYPE.KV;
}
}
export class KeyValue {
constructor(options) {
this.key = null;
this.value = null;
assign(this, options);
}
}
export class Assertions {
constructor(options) {
this.text = [];
this.regex = [];
this.responseTime = new ResponseTime();
assign(this, options);
}
}
class AssertionType {
constructor(type) {
this.type = type;
}
}
export class Text extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.TEXT);
this.subject = null;
this.condition = null;
this.value = null;
assign(this, options);
}
}
export class Regex extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.REGEX);
this.subject = null;
this.expression = null;
this.description = null;
assign(this, options);
}
}
export class ResponseTime extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.RESPONSE_TIME);
this.responseInTime = null;
assign(this, options);
}
isValid() {
return this.responseInTime !== null && this.responseInTime > 0;
}
}

View File

@ -45,7 +45,8 @@ export default {
'refresh': '刷新',
'remark': '备注',
'delete': '删除',
'not_filled': '未填写'
'not_filled': '未填写',
'please_select': '请选择',
},
workspace: {
'create': '创建工作空间',
@ -169,25 +170,43 @@ export default {
},
api_test: {
scenario: {
input_name: "请输入场景名称",
name: "场景名称",
base_url: "基础URL",
base_url_describe: "基础URL作为所有请求的URL前缀",
base_url_description: "基础URL作为所有请求的URL前缀",
variables: "变量",
headers: "请求头"
},
request: {
input_name: "请输入请求名称",
name: "请求名称",
method: "请求方法",
url: "请求URL",
url_describe: "例如: https://fit2cloud.com",
url_description: "例如: https://fit2cloud.com",
parameters: "请求参数",
parameters_desc: "参数追加到URL例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
headers: "请求头",
body: "请求内容",
body_kv: "键值对",
body_text: "文本",
assertions: "断言",
extract: "提取"
assertions: {
label: "断言",
text: "文本",
regex: "正则",
response_time: "响应时间",
select_type: "请选择类型",
select_subject: "请选择对象",
select_contains: "请选择条件",
contains: "包含",
not_contains: "不包含",
equals: "等于",
start_with: "以...开始",
end_with: "以...结束",
value: "值",
expression: "正则表达式",
response_in_time: "响应时间在...毫秒以内",
},
extract: "提取",
}
},
test_track: {