Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
e8842accc2
|
@ -10,27 +10,9 @@
|
||||||
</header>
|
</header>
|
||||||
<main v-if="this.isNotRunning">
|
<main v-if="this.isNotRunning">
|
||||||
<ms-metric-chart :content="content" :totalTime="totalTime"/>
|
<ms-metric-chart :content="content" :totalTime="totalTime"/>
|
||||||
<!--<el-container>
|
|
||||||
<el-aside width="900px">
|
|
||||||
<el-tabs v-model="activeName">
|
|
||||||
<el-tab-pane :label="$t('api_report.total')" name="total">
|
|
||||||
<ms-scenario-results :scenarios="content.scenarios"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane name="fail">
|
|
||||||
<template slot="label">
|
|
||||||
<span class="fail">{{ $t('api_report.fail') }}</span>
|
|
||||||
</template>
|
|
||||||
<ms-scenario-results :scenarios="fails"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</el-aside>
|
|
||||||
<el-main style="margin-top: 20px">
|
|
||||||
|
|
||||||
</el-main>
|
|
||||||
</el-container>-->
|
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-tabs v-model="activeName">
|
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||||
<el-tab-pane :label="$t('api_report.total')" name="total">
|
<el-tab-pane :label="$t('api_report.total')" name="total">
|
||||||
<ms-scenario-results :scenarios="content.scenarios" v-on:requestResult="requestResult"/>
|
<ms-scenario-results :scenarios="content.scenarios" v-on:requestResult="requestResult"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
@ -60,6 +42,7 @@
|
||||||
import MsScenarioResult from "./components/ScenarioResult";
|
import MsScenarioResult from "./components/ScenarioResult";
|
||||||
import MsMetricChart from "./components/MetricChart";
|
import MsMetricChart from "./components/MetricChart";
|
||||||
import MsScenarioResults from "./components/ScenarioResults";
|
import MsScenarioResults from "./components/ScenarioResults";
|
||||||
|
import {Scenario} from "../test/model/ScenarioModel";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiReportView",
|
name: "MsApiReportView",
|
||||||
|
@ -85,6 +68,9 @@
|
||||||
this.content = {};
|
this.content = {};
|
||||||
this.fails = [];
|
this.fails = [];
|
||||||
},
|
},
|
||||||
|
handleClick(tab, event) {
|
||||||
|
this.isRequestResult = false
|
||||||
|
},
|
||||||
getReport() {
|
getReport() {
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
|
@ -134,9 +120,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestResult(requestResult) {
|
requestResult(requestResult) {
|
||||||
|
this.isRequestResult = false;
|
||||||
|
this.$nextTick(function () {
|
||||||
this.isRequestResult = true;
|
this.isRequestResult = true;
|
||||||
this.request = requestResult.request;
|
this.request = requestResult.request;
|
||||||
this.scenarioName = requestResult.scenarioName;
|
this.scenarioName = requestResult.scenarioName;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,12 @@
|
||||||
<el-row type="flex" align="middle">
|
<el-row type="flex" align="middle">
|
||||||
<div style="width: 50%">
|
<div style="width: 50%">
|
||||||
<el-row type="flex" justify="center" align="middle">
|
<el-row type="flex" justify="center" align="middle">
|
||||||
|
<el-row>
|
||||||
<div class="metric-time">
|
<div class="metric-time">
|
||||||
<div class="value" style="margin-left: -150px">{{time}}</div>
|
<div class="value" style="margin-right: 50px">{{time}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<chart id="chart" ref="chart" :options="options" :autoresize="true"></chart>
|
<chart id="chart" ref="chart" :options="options" :autoresize="true"></chart>
|
||||||
<el-row type="flex" justify="center" align="middle">
|
<el-row type="flex" justify="center" align="middle">
|
||||||
<i class="circle success"/>
|
<i class="circle success"/>
|
||||||
|
|
|
@ -39,17 +39,18 @@
|
||||||
methods: {
|
methods: {
|
||||||
active() {
|
active() {
|
||||||
this.$emit("requestResult", {request: this.request, scenarioName: this.scenarioName});
|
this.$emit("requestResult", {request: this.request, scenarioName: this.scenarioName});
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
/* computed: {
|
||||||
assertion() {
|
assertion() {
|
||||||
return this.request.passAssertions + " / " + this.request.totalAssertions;
|
return this.request.passAssertions + " / " + this.request.totalAssertions;
|
||||||
},
|
},
|
||||||
hasSub() {
|
hasSub() {
|
||||||
return this.request.subRequestResults.length > 0;
|
return this.request.subRequestResults.length > 0;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isActive: false,
|
isActive: true,
|
||||||
activeName: "sub",
|
activeName: "sub",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isActive: false,
|
isActive: true,
|
||||||
activeName: "body",
|
activeName: "body",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isActive: false,
|
isActive: true,
|
||||||
activeName: "body",
|
activeName: "body",
|
||||||
modes: ['text', 'json', 'xml', 'html'],
|
modes: ['text', 'json', 'xml', 'html'],
|
||||||
mode: BODY_FORMAT.TEXT
|
mode: BODY_FORMAT.TEXT
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
<ms-dropdown :default-command="body.format" v-if="body.type == 'Raw'" :commands="modes" @command="modeChange"/>
|
<ms-dropdown :default-command="body.format" v-if="body.type == 'Raw'" :commands="modes" @command="modeChange"/>
|
||||||
|
|
||||||
<ms-api-key-value :is-read-only="isReadOnly" :items="body.kvs" v-if="body.isKV()"/>
|
<ms-api-variable :is-read-only="isReadOnly" :items="body.kvs" v-if="body.isKV()"/>
|
||||||
|
|
||||||
<div class="body-raw" v-if="body.type == 'Raw'">
|
<div class="body-raw" v-if="body.type == 'Raw'">
|
||||||
<ms-code-edit :mode="body.format" :read-only="isReadOnly" :data.sync="body.raw" :modes="modes" ref="codeEdit"/>
|
<ms-code-edit :mode="body.format" :read-only="isReadOnly" :data.sync="body.raw" :modes="modes" ref="codeEdit"/>
|
||||||
|
@ -25,10 +25,11 @@
|
||||||
import {Body, BODY_FORMAT, BODY_TYPE} from "../model/ScenarioModel";
|
import {Body, BODY_FORMAT, BODY_TYPE} from "../model/ScenarioModel";
|
||||||
import MsCodeEdit from "../../../common/components/MsCodeEdit";
|
import MsCodeEdit from "../../../common/components/MsCodeEdit";
|
||||||
import MsDropdown from "../../../common/components/MsDropdown";
|
import MsDropdown from "../../../common/components/MsDropdown";
|
||||||
|
import MsApiVariable from "@/business/components/api/test/components/ApiVariable";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiBody",
|
name: "MsApiBody",
|
||||||
components: {MsDropdown, MsCodeEdit, MsApiKeyValue},
|
components: {MsApiVariable, MsDropdown, MsCodeEdit, MsApiKeyValue},
|
||||||
props: {
|
props: {
|
||||||
body: Body,
|
body: Body,
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
|
|
|
@ -15,18 +15,8 @@
|
||||||
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col>
|
<el-col>
|
||||||
<el-autocomplete
|
<el-input :disabled="isReadOnly" v-model="item.value" size="small" @change="change"
|
||||||
:disabled="isReadOnly"
|
:placeholder="valueText" show-word-limit/>
|
||||||
size="small"
|
|
||||||
class="input-with-autocomplete"
|
|
||||||
v-model="item.value"
|
|
||||||
:fetch-suggestions="funcSearch"
|
|
||||||
:placeholder="valueText"
|
|
||||||
value-key="name"
|
|
||||||
highlight-first-item
|
|
||||||
@select="change">
|
|
||||||
<i slot="suffix" class="el-input__icon el-icon-edit" style="cursor: pointer;" @click="advanced(item)"></i>
|
|
||||||
</el-autocomplete>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col class="kv-delete">
|
<el-col class="kv-delete">
|
||||||
<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)"
|
||||||
|
@ -34,59 +24,11 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-dialog :title="$t('api_test.request.parameters_advance')"
|
|
||||||
:visible.sync="itemValueVisible"
|
|
||||||
class="advanced-item-value"
|
|
||||||
width="50%">
|
|
||||||
<el-form>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input :autosize="{ minRows: 2, maxRows: 4}" type="textarea" :placeholder="valueText"
|
|
||||||
v-model="itemValue"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<div>
|
|
||||||
<el-row type="flex" align="middle">
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-button size="small" type="primary" plain @click="saveAdvanced()">
|
|
||||||
{{ $t('commons.save') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button size="small" type="success" plain @click="showPreview(itemValue)">
|
|
||||||
{{ $t('api_test.request.parameters_preview') }}
|
|
||||||
</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col>
|
|
||||||
<div> {{ itemValuePreview }}</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="format-tip">
|
|
||||||
<div>
|
|
||||||
<p>{{ $t('api_test.request.parameters_filter') }}:
|
|
||||||
<el-tag size="mini" v-for="func in funcs" :key="func" @click="appendFunc(func)"
|
|
||||||
style="margin-left: 2px;cursor: pointer;">
|
|
||||||
<span>{{ func }}</span>
|
|
||||||
</el-tag>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>{{ $t('api_test.request.parameters_filter_desc') }}:
|
|
||||||
<el-link href="http://mockjs.com/examples.html" target="_blank">http://mockjs.com/examples.html</el-link>
|
|
||||||
</span>
|
|
||||||
<p>{{ $t('api_test.request.parameters_filter_example') }}:@string(10) | md5 | substr: 1, 3</p>
|
|
||||||
<p>{{ $t('api_test.request.parameters_filter_example') }}:@integer(1, 5) | concat:_metersphere</p>
|
|
||||||
<p><strong>{{ $t('api_test.request.parameters_filter_tips') }}</strong></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {KeyValue} from "../model/ScenarioModel";
|
import {KeyValue} from "../model/ScenarioModel";
|
||||||
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
|
|
||||||
import {calculate} from "@/business/components/api/test/model/ScenarioModel";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiKeyValue",
|
name: "MsApiKeyValue",
|
||||||
|
@ -103,16 +45,6 @@ export default {
|
||||||
suggestions: Array
|
suggestions: Array
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
itemValueVisible: false,
|
|
||||||
itemValue: null,
|
|
||||||
funcs: ["md5", "sha1", "sha224", "sha256", "sha384", "sha512", "base64",
|
|
||||||
"unbase64", "substr", "concat", "lconcat", "lower", "upper", "length", "number"],
|
|
||||||
itemValuePreview: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
keyText() {
|
keyText() {
|
||||||
return this.keyPlaceholder || this.$t("api_test.key");
|
return this.keyPlaceholder || this.$t("api_test.key");
|
||||||
|
@ -159,37 +91,6 @@ export default {
|
||||||
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
|
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
funcSearch(queryString, cb) {
|
|
||||||
let funcs = MOCKJS_FUNC.concat(JMETER_FUNC);
|
|
||||||
let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs;
|
|
||||||
// 调用 callback 返回建议列表的数据
|
|
||||||
cb(results);
|
|
||||||
},
|
|
||||||
funcFilter(queryString) {
|
|
||||||
return (func) => {
|
|
||||||
return (func.name.toLowerCase().indexOf(queryString.toLowerCase()) > -1);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
showPreview(itemValue) {
|
|
||||||
this.itemValuePreview = calculate(itemValue);
|
|
||||||
},
|
|
||||||
appendFunc(func) {
|
|
||||||
if (this.itemValue) {
|
|
||||||
this.itemValue += " | " + func;
|
|
||||||
} else {
|
|
||||||
this.$warning(this.$t("api_test.request.parameters_preview_warning"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
advanced(item) {
|
|
||||||
this.currentItem = item;
|
|
||||||
this.itemValueVisible = true;
|
|
||||||
this.itemValue = item.value;
|
|
||||||
this.itemValuePreview = null;
|
|
||||||
},
|
|
||||||
saveAdvanced() {
|
|
||||||
this.currentItem.value = this.itemValue;
|
|
||||||
this.itemValueVisible = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.items.length === 0) {
|
if (this.items.length === 0) {
|
||||||
|
@ -215,19 +116,4 @@ export default {
|
||||||
.el-autocomplete {
|
.el-autocomplete {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-item-value >>> .el-dialog__body {
|
|
||||||
padding: 15px 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.format-tip {
|
|
||||||
background: #EDEDED;
|
|
||||||
}
|
|
||||||
|
|
||||||
.format-tip {
|
|
||||||
border: solid #E1E1E1 1px;
|
|
||||||
margin: 10px 0;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -36,7 +36,8 @@
|
||||||
<el-main class="scenario-main">
|
<el-main class="scenario-main">
|
||||||
<div class="scenario-form">
|
<div class="scenario-form">
|
||||||
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" :project-id="projectId" v-if="isScenario"/>
|
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" :project-id="projectId" v-if="isScenario"/>
|
||||||
<ms-api-request-form :debug-report-id="debugReportId" @runDebug="runDebug" :is-read-only="isReadOnly" :request="selected" v-if="isRequest"/>
|
<ms-api-request-form :debug-report-id="debugReportId" @runDebug="runDebug" :is-read-only="isReadOnly"
|
||||||
|
:request="selected" :scenario="currentScenario" v-if="isRequest"/>
|
||||||
</div>
|
</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
|
@ -49,7 +50,7 @@
|
||||||
import MsApiRequestConfig from "./request/ApiRequestConfig";
|
import MsApiRequestConfig from "./request/ApiRequestConfig";
|
||||||
import MsApiRequestForm from "./request/ApiRequestForm";
|
import MsApiRequestForm from "./request/ApiRequestForm";
|
||||||
import MsApiScenarioForm from "./ApiScenarioForm";
|
import MsApiScenarioForm from "./ApiScenarioForm";
|
||||||
import {Scenario, Request} from "../model/ScenarioModel";
|
import {Request, Scenario} from "../model/ScenarioModel";
|
||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('api_test.scenario.headers')" name="headers">
|
<el-tab-pane :label="$t('api_test.scenario.headers')" name="headers">
|
||||||
<ms-api-key-value :is-read-only="isReadOnly" :items="scenario.headers" :suggestions="headerSuggestions"
|
<ms-api-key-value :is-read-only="isReadOnly" :items="scenario.headers" :suggestions="headerSuggestions"
|
||||||
|
:environment="scenario.environment"
|
||||||
:description="$t('api_test.scenario.kv_description')"/>
|
:description="$t('api_test.scenario.kv_description')"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('api_test.scenario.dubbo')" name="dubbo">
|
<el-tab-pane :label="$t('api_test.scenario.dubbo')" name="dubbo">
|
||||||
|
|
|
@ -0,0 +1,343 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<span class="kv-description" v-if="description">
|
||||||
|
{{ description }}
|
||||||
|
</span>
|
||||||
|
<div class="kv-row" v-for="(item, index) in items" :key="index">
|
||||||
|
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||||
|
<el-col>
|
||||||
|
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
|
||||||
|
@change="change"
|
||||||
|
:placeholder="keyText" show-word-limit/>
|
||||||
|
<el-autocomplete :disabled="isReadOnly" :maxlength="200" v-if="suggestions" v-model="item.name" size="small"
|
||||||
|
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText"
|
||||||
|
show-word-limit/>
|
||||||
|
|
||||||
|
</el-col>
|
||||||
|
<el-col>
|
||||||
|
<el-autocomplete
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
size="small"
|
||||||
|
class="input-with-autocomplete"
|
||||||
|
v-model="item.value"
|
||||||
|
:fetch-suggestions="funcSearch"
|
||||||
|
:placeholder="valueText"
|
||||||
|
value-key="name"
|
||||||
|
highlight-first-item
|
||||||
|
@select="change">
|
||||||
|
<i slot="suffix" class="el-input__icon el-icon-edit" style="cursor: pointer;" @click="advanced(item)"></i>
|
||||||
|
</el-autocomplete>
|
||||||
|
</el-col>
|
||||||
|
<el-col class="kv-delete">
|
||||||
|
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||||
|
:disabled="isDisable(index) || isReadOnly"/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog :title="$t('api_test.request.parameters_advance')"
|
||||||
|
:visible.sync="itemValueVisible"
|
||||||
|
class="advanced-item-value"
|
||||||
|
width="70%">
|
||||||
|
<el-tabs tab-position="top" style="height: 50vh;">
|
||||||
|
<el-tab-pane :label="$t('api_test.request.parameters_advance_mock')">
|
||||||
|
<el-row type="flex" :gutter="20" style="overflow-x: auto;">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-autocomplete
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
size="small"
|
||||||
|
class="input-with-autocomplete"
|
||||||
|
v-model="itemValue"
|
||||||
|
:fetch-suggestions="funcSearch"
|
||||||
|
:placeholder="valueText"
|
||||||
|
value-key="name"
|
||||||
|
highlight-first-item
|
||||||
|
@select="change">
|
||||||
|
</el-autocomplete>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" v-for="(itemFunc, itemIndex) in itemFuncs" :key="itemIndex">
|
||||||
|
<div v-for="(func, funcIndex) in funcs"
|
||||||
|
:key="`${itemIndex}-${funcIndex}`">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-radio size="mini" v-model="itemFunc.name" :label="func.name"
|
||||||
|
@change="methodChange(itemFunc, func)"/>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" v-if="itemFunc.name === func.name">
|
||||||
|
<div v-for="(p, pIndex) in itemFunc.params" :key="`${itemIndex}-${funcIndex}-${pIndex}`">
|
||||||
|
<el-input :placeholder="p.name" size="mini" v-model="p.value" @change="showPreview"/>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="变量">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div v-if="environment">
|
||||||
|
<el-tree :data="environmentParams" :props="{ children: 'children', label: 'name'}"></el-tree>
|
||||||
|
</div>
|
||||||
|
<div v-if="scenario">
|
||||||
|
<el-tree :data="scenarioParams" :props="{ children: 'children', label: 'name'}"></el-tree>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<div style="padding-top: 10px;">
|
||||||
|
<el-row type="flex" align="middle">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-button size="small" type="primary" plain @click="saveAdvanced()">
|
||||||
|
{{ $t('commons.save') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" type="info" plain @click="addFunc()">
|
||||||
|
{{ $t('api_test.request.parameters_advance_add_func') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" type="success" plain @click="showPreview()">
|
||||||
|
{{ $t('api_test.request.parameters_preview') }}
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col>
|
||||||
|
<div> {{ itemValuePreview }}</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {KeyValue, Scenario} from "../model/ScenarioModel";
|
||||||
|
import {MOCKJS_FUNC} from "@/common/js/constants";
|
||||||
|
import {calculate} from "@/business/components/api/test/model/ScenarioModel";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MsApiVariable",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
keyPlaceholder: String,
|
||||||
|
valuePlaceholder: String,
|
||||||
|
description: String,
|
||||||
|
items: Array,
|
||||||
|
environment: Object,
|
||||||
|
scenario: Scenario,
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
suggestions: Array
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.scenario) {
|
||||||
|
let variables = this.scenario.variables;
|
||||||
|
this.scenarioParams = [
|
||||||
|
{
|
||||||
|
name: this.scenario.name,
|
||||||
|
children: variables.filter(v => v.name),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (this.environment) {
|
||||||
|
let variables = JSON.parse(this.environment.variables);
|
||||||
|
this.environmentParams = [
|
||||||
|
{
|
||||||
|
name: this.environment.name,
|
||||||
|
children: variables.filter(v => v.name),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
itemValueVisible: false,
|
||||||
|
itemValue: null,
|
||||||
|
funcs: [
|
||||||
|
{name: "md5"},
|
||||||
|
{name: "base64"},
|
||||||
|
{name: "unbase64"},
|
||||||
|
{
|
||||||
|
name: "substr",
|
||||||
|
params: [{name: "start"}, {name: "length"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "concat",
|
||||||
|
params: [{name: "suffix"}]
|
||||||
|
},
|
||||||
|
{name: "lconcat", params: [{name: "prefix"}]},
|
||||||
|
{name: "sha1"},
|
||||||
|
{name: "sha224"},
|
||||||
|
{name: "sha256"},
|
||||||
|
{name: "sha384"},
|
||||||
|
{name: "sha512"},
|
||||||
|
{name: "lower"},
|
||||||
|
{name: "upper"},
|
||||||
|
{name: "length"},
|
||||||
|
{name: "number"}
|
||||||
|
],
|
||||||
|
itemValuePreview: null,
|
||||||
|
itemFuncs: [],
|
||||||
|
currentFunc: "",
|
||||||
|
mockFuncs: MOCKJS_FUNC,
|
||||||
|
environmentParams: [],
|
||||||
|
scenarioParams: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
keyText() {
|
||||||
|
return this.keyPlaceholder || this.$t("api_test.key");
|
||||||
|
},
|
||||||
|
valueText() {
|
||||||
|
return this.valuePlaceholder || this.$t("api_test.value");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
remove: function (index) {
|
||||||
|
this.items.splice(index, 1);
|
||||||
|
this.$emit('change', this.items);
|
||||||
|
},
|
||||||
|
change: function () {
|
||||||
|
let isNeedCreate = true;
|
||||||
|
let removeIndex = -1;
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
if (!item.name && !item.value) {
|
||||||
|
// 多余的空行
|
||||||
|
if (index !== this.items.length - 1) {
|
||||||
|
removeIndex = index;
|
||||||
|
}
|
||||||
|
// 没有空行,需要创建空行
|
||||||
|
isNeedCreate = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (isNeedCreate) {
|
||||||
|
this.items.push(new KeyValue());
|
||||||
|
}
|
||||||
|
this.$emit('change', this.items);
|
||||||
|
// TODO 检查key重复
|
||||||
|
},
|
||||||
|
isDisable: function (index) {
|
||||||
|
return this.items.length - 1 === index;
|
||||||
|
},
|
||||||
|
querySearch(queryString, cb) {
|
||||||
|
let suggestions = this.suggestions;
|
||||||
|
let results = queryString ? suggestions.filter(this.createFilter(queryString)) : suggestions;
|
||||||
|
cb(results);
|
||||||
|
},
|
||||||
|
createFilter(queryString) {
|
||||||
|
return (restaurant) => {
|
||||||
|
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
funcSearch(queryString, cb) {
|
||||||
|
let funcs = MOCKJS_FUNC;
|
||||||
|
let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs;
|
||||||
|
// 调用 callback 返回建议列表的数据
|
||||||
|
cb(results);
|
||||||
|
},
|
||||||
|
funcFilter(queryString) {
|
||||||
|
return (func) => {
|
||||||
|
return (func.name.toLowerCase().indexOf(queryString.toLowerCase()) > -1);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
showPreview() {
|
||||||
|
// 找到变量本身
|
||||||
|
if (!this.itemValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let index = this.itemValue.indexOf("|");
|
||||||
|
if (index > -1) {
|
||||||
|
this.itemValue = this.itemValue.substring(0, index).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.itemFuncs.forEach(f => {
|
||||||
|
if (!f.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.itemValue += "|" + f.name;
|
||||||
|
if (f.params) {
|
||||||
|
this.itemValue += ":" + f.params.map(p => p.value).join(",");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.itemValuePreview = calculate(this.itemValue);
|
||||||
|
},
|
||||||
|
methodChange(itemFunc, func) {
|
||||||
|
let index = this.itemFuncs.indexOf(itemFunc);
|
||||||
|
this.itemFuncs = this.itemFuncs.slice(0, index);
|
||||||
|
// 这里要用 deep copy
|
||||||
|
this.itemFuncs.push(JSON.parse(JSON.stringify(func)));
|
||||||
|
this.showPreview();
|
||||||
|
},
|
||||||
|
addFunc() {
|
||||||
|
if (this.itemFuncs.length > 4) {
|
||||||
|
this.$info(this.$t('api_test.request.parameters_advance_add_func_limit'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.itemFuncs.length > 0) {
|
||||||
|
let func = this.itemFuncs[this.itemFuncs.length - 1];
|
||||||
|
if (!func.name) {
|
||||||
|
this.$warning(this.$t('api_test.request.parameters_advance_add_func_error'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (func.params) {
|
||||||
|
for (let j = 0; j < func.params.length; j++) {
|
||||||
|
if (!func.params[j].value) {
|
||||||
|
this.$warning(this.$t('api_test.request.parameters_advance_add_param_error'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.itemFuncs.push({name: '', params: []});
|
||||||
|
},
|
||||||
|
advanced(item) {
|
||||||
|
this.currentItem = item;
|
||||||
|
this.itemValueVisible = true;
|
||||||
|
this.itemValue = '';
|
||||||
|
this.itemValuePreview = null;
|
||||||
|
this.itemFuncs = [];
|
||||||
|
},
|
||||||
|
saveAdvanced() {
|
||||||
|
this.currentItem.value = this.itemValue;
|
||||||
|
this.itemValueVisible = false;
|
||||||
|
this.itemFuncs = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.items.length === 0) {
|
||||||
|
this.items.push(new KeyValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.kv-description {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kv-row {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kv-delete {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-autocomplete {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-item-value >>> .el-dialog__body {
|
||||||
|
padding: 15px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-row {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -28,7 +28,8 @@
|
||||||
<el-tag class="environment-display">
|
<el-tag class="environment-display">
|
||||||
<span class="environment-name">{{ request.environment ? request.environment.name + ': ' : '' }}</span>
|
<span class="environment-name">{{ request.environment ? request.environment.name + ': ' : '' }}</span>
|
||||||
<span class="environment-url">{{ displayUrl }}</span>
|
<span class="environment-url">{{ displayUrl }}</span>
|
||||||
<span v-if="!displayUrl" class="environment-url-tip">{{$t('api_test.request.please_configure_environment_in_scenario')}}</span>
|
<span v-if="!displayUrl"
|
||||||
|
class="environment-url-tip">{{ $t('api_test.request.please_configure_environment_in_scenario') }}</span>
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
@ -39,11 +40,14 @@
|
||||||
</el-switch>
|
</el-switch>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-button class="debug-button" size="small" type="primary" @click="runDebug">{{$t('load_test.save_and_run')}}</el-button>
|
<el-button class="debug-button" size="small" type="primary" @click="runDebug">{{ $t('load_test.save_and_run') }}
|
||||||
|
</el-button>
|
||||||
|
|
||||||
<el-tabs v-model="activeName">
|
<el-tabs v-model="activeName">
|
||||||
<el-tab-pane :label="$t('api_test.request.parameters')" name="parameters">
|
<el-tab-pane :label="$t('api_test.request.parameters')" name="parameters">
|
||||||
<ms-api-key-value :is-read-only="isReadOnly" :items="request.parameters"
|
<ms-api-variable :is-read-only="isReadOnly" :items="request.parameters"
|
||||||
|
:environment="request.environment"
|
||||||
|
:scenario="scenario"
|
||||||
:description="$t('api_test.request.parameters_desc')"/>
|
:description="$t('api_test.request.parameters_desc')"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('api_test.request.headers')" name="headers">
|
<el-tab-pane :label="$t('api_test.request.headers')" name="headers">
|
||||||
|
@ -66,16 +70,18 @@
|
||||||
import MsApiKeyValue from "../ApiKeyValue";
|
import MsApiKeyValue from "../ApiKeyValue";
|
||||||
import MsApiBody from "../ApiBody";
|
import MsApiBody from "../ApiBody";
|
||||||
import MsApiAssertions from "../assertion/ApiAssertions";
|
import MsApiAssertions from "../assertion/ApiAssertions";
|
||||||
import {HttpRequest, KeyValue} from "../../model/ScenarioModel";
|
import {HttpRequest, KeyValue, Scenario} from "../../model/ScenarioModel";
|
||||||
import MsApiExtract from "../extract/ApiExtract";
|
import MsApiExtract from "../extract/ApiExtract";
|
||||||
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
|
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
|
||||||
import {REQUEST_HEADERS} from "@/common/js/constants";
|
import {REQUEST_HEADERS} from "@/common/js/constants";
|
||||||
|
import MsApiVariable from "@/business/components/api/test/components/ApiVariable";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiHttpRequestForm",
|
name: "MsApiHttpRequestForm",
|
||||||
components: {ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
|
components: {MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
|
||||||
props: {
|
props: {
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
|
scenario: Scenario,
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="request-form">
|
<div class="request-form">
|
||||||
<component @runDebug="runDebug" :is="component" :is-read-only="isReadOnly" :request="request"/>
|
<component @runDebug="runDebug" :is="component" :is-read-only="isReadOnly" :request="request" :scenario="scenario"/>
|
||||||
<ms-scenario-results v-loading="debugReportLoading" v-if="isCompleted" :scenarios="isCompleted ? request.debugReport.scenarios : []"/>
|
<ms-scenario-results v-loading="debugReportLoading" v-if="isCompleted" :scenarios="isCompleted ? request.debugReport.scenarios : []"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {Request, RequestFactory} from "../../model/ScenarioModel";
|
import {Request, RequestFactory, Scenario} from "../../model/ScenarioModel";
|
||||||
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
|
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
|
||||||
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
|
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
|
||||||
import MsScenarioResults from "../../../report/components/ScenarioResults";
|
import MsScenarioResults from "../../../report/components/ScenarioResults";
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
name: "MsApiRequestForm",
|
name: "MsApiRequestForm",
|
||||||
components: {MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm},
|
components: {MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm},
|
||||||
props: {
|
props: {
|
||||||
|
scenario: Scenario,
|
||||||
request: Request,
|
request: Request,
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
|
@ -873,9 +873,6 @@ class JMXGenerator {
|
||||||
this.addEnvironments(environment.headers, scenario.headers)
|
this.addEnvironments(environment.headers, scenario.headers)
|
||||||
}
|
}
|
||||||
let headers = this.filterKV(scenario.headers);
|
let headers = this.filterKV(scenario.headers);
|
||||||
headers.forEach(h => {
|
|
||||||
h.value = calculate(h.value);
|
|
||||||
});
|
|
||||||
if (headers.length > 0) {
|
if (headers.length > 0) {
|
||||||
let name = scenario.name + " Headers"
|
let name = scenario.name + " Headers"
|
||||||
threadGroup.put(new HeaderManager(name, headers));
|
threadGroup.put(new HeaderManager(name, headers));
|
||||||
|
@ -886,9 +883,6 @@ class JMXGenerator {
|
||||||
let name = request.name + " Headers";
|
let name = request.name + " Headers";
|
||||||
this.addBodyFormat(request);
|
this.addBodyFormat(request);
|
||||||
let headers = this.filterKV(request.headers);
|
let headers = this.filterKV(request.headers);
|
||||||
headers.forEach(h => {
|
|
||||||
h.value = calculate(h.value);
|
|
||||||
});
|
|
||||||
if (headers.length > 0) {
|
if (headers.length > 0) {
|
||||||
httpSamplerProxy.put(new HeaderManager(name, headers));
|
httpSamplerProxy.put(new HeaderManager(name, headers));
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ export const funcFilters = {
|
||||||
|
|
||||||
lconcat: function (str, ...args) {
|
lconcat: function (str, ...args) {
|
||||||
args.forEach(item => {
|
args.forEach(item => {
|
||||||
str = item + this._string;
|
str = item + str;
|
||||||
});
|
});
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
|
@ -403,6 +403,11 @@ export default {
|
||||||
parameters_advance: "Advanced parameter settings",
|
parameters_advance: "Advanced parameter settings",
|
||||||
parameters_preview: "Preview",
|
parameters_preview: "Preview",
|
||||||
parameters_preview_warning: "Please enter the template first",
|
parameters_preview_warning: "Please enter the template first",
|
||||||
|
parameters_advance_mock: "Mock Data",
|
||||||
|
parameters_advance_add_func: "Add Function",
|
||||||
|
parameters_advance_add_func_limit: "Support up to 5 functions",
|
||||||
|
parameters_advance_add_func_error: "Please select function first",
|
||||||
|
parameters_advance_add_param_error: "Please enter function parameters",
|
||||||
parameters_desc: "Parameters will be appended to the URL e.g. https://fit2cloud.com?Name=Value&Name2=Value2",
|
parameters_desc: "Parameters will be appended to the URL e.g. https://fit2cloud.com?Name=Value&Name2=Value2",
|
||||||
headers: "Headers",
|
headers: "Headers",
|
||||||
body: "Body",
|
body: "Body",
|
||||||
|
|
|
@ -406,6 +406,11 @@ export default {
|
||||||
parameters_advance: "高级参数设置",
|
parameters_advance: "高级参数设置",
|
||||||
parameters_preview: "预览",
|
parameters_preview: "预览",
|
||||||
parameters_preview_warning: "请先输入模版",
|
parameters_preview_warning: "请先输入模版",
|
||||||
|
parameters_advance_mock: "Mock 数据",
|
||||||
|
parameters_advance_add_func: "添加函数",
|
||||||
|
parameters_advance_add_func_limit: "最多支持5个函数",
|
||||||
|
parameters_advance_add_func_error: "请先选择函数",
|
||||||
|
parameters_advance_add_param_error: "请输入函数参数",
|
||||||
parameters_desc: "参数追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
|
parameters_desc: "参数追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
|
||||||
headers: "请求头",
|
headers: "请求头",
|
||||||
body: "请求内容",
|
body: "请求内容",
|
||||||
|
|
|
@ -403,6 +403,11 @@ export default {
|
||||||
parameters_advance: "高級參數設置",
|
parameters_advance: "高級參數設置",
|
||||||
parameters_preview: "預覽",
|
parameters_preview: "預覽",
|
||||||
parameters_preview_warning: "請先輸入模版",
|
parameters_preview_warning: "請先輸入模版",
|
||||||
|
parameters_advance_mock: "Mock 數據",
|
||||||
|
parameters_advance_add_func: "添加函數",
|
||||||
|
parameters_advance_add_func_limit: "最多支持5個函數",
|
||||||
|
parameters_advance_add_func_error: "請先選擇函數",
|
||||||
|
parameters_advance_add_param_error: "請輸入函數參數",
|
||||||
parameters_desc: "參數追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
|
parameters_desc: "參數追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
|
||||||
headers: "請求頭",
|
headers: "請求頭",
|
||||||
body: "請求內容",
|
body: "請求內容",
|
||||||
|
|
Loading…
Reference in New Issue