fix(测试跟踪): 测试计划报告导出接口测试报告无法打开
--bug=1018685 --user=陈建星 【测试跟踪】测试计划-测试报告-导出-接口和性能的报告无法打开 https://www.tapd.cn/55049933/s/1273189
This commit is contained in:
parent
df34484cc6
commit
2990b81492
|
@ -0,0 +1,210 @@
|
|||
<template>
|
||||
<div>
|
||||
<span class="kv-description" v-if="description">
|
||||
{{ description }}
|
||||
</span>
|
||||
|
||||
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
<el-col class="kv-checkbox" v-if="isShowEnable">
|
||||
<input type="checkbox" v-model="item.enable" :disabled="isReadOnly"/>
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<el-input v-model="item.description" size="small" maxlength="200"
|
||||
:placeholder="$t('commons.description')" show-word-limit>
|
||||
</el-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<ms-api-body-file-upload :parameter="item"/>
|
||||
</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>
|
||||
<ms-api-variable-advance ref="variableAdvance" :environment="environment" :scenario="scenario"
|
||||
:parameters="parameters"
|
||||
:current-item="currentItem"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {KeyValue, Scenario} from "metersphere-frontend/src/model/ApiTestModel";
|
||||
import {JMETER_FUNC, MOCKJS_FUNC} from "metersphere-frontend/src/utils/constants";
|
||||
import MsApiVariableAdvance from "metersphere-frontend/src/components/environment/commons/ApiVariableAdvance";
|
||||
import MsApiBodyFileUpload from "metersphere-frontend/src/components/environment/commons/ApiBodyFileUpload";
|
||||
import {REQUIRED} from "metersphere-frontend/src/model/JsonData";
|
||||
|
||||
export default {
|
||||
name: "MsApiVariable",
|
||||
components: {MsApiBodyFileUpload, MsApiVariableAdvance},
|
||||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
description: String,
|
||||
parameters: Array,
|
||||
rest: Array,
|
||||
environment: Object,
|
||||
scenario: Scenario,
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShowEnable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
suggestions: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentItem: null,
|
||||
requireds: REQUIRED
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
keyText() {
|
||||
return this.keyPlaceholder || this.$t("api_test.key");
|
||||
},
|
||||
valueText() {
|
||||
return this.valuePlaceholder || this.$t("api_test.value");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove: function (index) {
|
||||
// 移除整行输入控件及内容
|
||||
this.parameters.splice(index, 1);
|
||||
this.$emit('change', this.parameters);
|
||||
},
|
||||
change: function () {
|
||||
let isNeedCreate = true;
|
||||
let removeIndex = -1;
|
||||
this.parameters.forEach((item, index) => {
|
||||
if (!item.name && !item.value) {
|
||||
// 多余的空行
|
||||
if (index !== this.parameters.length - 1) {
|
||||
removeIndex = index;
|
||||
}
|
||||
// 没有空行,需要创建空行
|
||||
isNeedCreate = false;
|
||||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'text/plain'
|
||||
}));
|
||||
}
|
||||
this.$emit('change', this.parameters);
|
||||
// TODO 检查key重复
|
||||
},
|
||||
isDisable: function (index) {
|
||||
return this.parameters.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.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);
|
||||
};
|
||||
},
|
||||
uuid: function () {
|
||||
return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1);
|
||||
},
|
||||
advanced(item) {
|
||||
this.$refs.variableAdvance.open();
|
||||
this.currentItem = item;
|
||||
},
|
||||
typeChange(item) {
|
||||
if (item.type === 'file') {
|
||||
item.contentType = 'application/octet-stream';
|
||||
} else {
|
||||
item.contentType = 'text/plain';
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'file',
|
||||
enable: true,
|
||||
required: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'application/octet-stream'
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kv-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kv-delete {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.kv-select {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.el-autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kv-checkbox {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.advanced-item-value :deep(.el-dialog__body) {
|
||||
padding: 15px 25px;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.kv-type {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
color: #1E90FF;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,343 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-radio-group v-model="body.type" size="mini">
|
||||
<el-radio :disabled="isReadOnly" :label="type.FORM_DATA" @change="modeChange">
|
||||
{{ $t('api_test.definition.request.body_form_data') }}
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.WWW_FORM" @change="modeChange">
|
||||
{{ $t('api_test.definition.request.body_x_www_from_urlencoded') }}
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.JSON" @change="modeChange">
|
||||
{{ $t('api_test.definition.request.body_json') }}
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.XML" @change="modeChange">
|
||||
{{ $t('api_test.definition.request.body_xml') }}
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.RAW" @change="modeChange">
|
||||
{{ $t('api_test.definition.request.body_raw') }}
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.BINARY" @change="modeChange">
|
||||
{{ $t('api_test.definition.request.body_binary') }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="body.type == 'Form Data' || body.type == 'WWW_FORM'">
|
||||
<el-row v-if="body.type == 'Form Data' || body.type == 'WWW_FORM'">
|
||||
<el-link class="ms-el-link" @click="batchAdd"> {{ $t("commons.batch_add") }}</el-link>
|
||||
</el-row>
|
||||
<ms-api-variable
|
||||
:with-more-setting="true"
|
||||
:is-read-only="isReadOnly"
|
||||
:parameters="body.kvs"
|
||||
:urlEncode="body.type == 'WWW_FORM'"
|
||||
:isShowEnable="isShowEnable"
|
||||
:scenario-definition="scenarioDefinition"
|
||||
:id="id"
|
||||
@editScenarioAdvance="editScenarioAdvance"
|
||||
type="body"/>
|
||||
</div>
|
||||
|
||||
<div class="ms-body" v-if="body.type == 'XML'">
|
||||
<ms-code-edit
|
||||
:read-only="isReadOnly"
|
||||
:data.sync="body.raw"
|
||||
:modes="modes"
|
||||
:mode="'text'"
|
||||
ref="codeEdit"/>
|
||||
</div>
|
||||
|
||||
<div class="ms-body" v-if="body.type == 'Raw'">
|
||||
<ms-code-edit
|
||||
:read-only="isReadOnly"
|
||||
:data.sync="body.raw"
|
||||
:modes="modes"
|
||||
ref="codeEdit"/>
|
||||
</div>
|
||||
|
||||
<ms-api-binary-variable
|
||||
:is-read-only="isReadOnly"
|
||||
:parameters="body.binary"
|
||||
:isShowEnable="isShowEnable"
|
||||
type="body"
|
||||
v-if="body.type == 'BINARY'"/>
|
||||
<batch-add-parameter @batchSave="batchSave" ref="batchAddParameter"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValue from "metersphere-frontend/src/components/environment/commons/ApiKeyValue";
|
||||
import {BODY_TYPE, KeyValue} from "metersphere-frontend/src//model/ApiTestModel";
|
||||
import MsCodeEdit from "metersphere-frontend/src/components/MsCodeEdit";
|
||||
import MsDropdown from "metersphere-frontend/src/components/MsDropdown";
|
||||
import MsApiVariable from "metersphere-frontend/src/components/environment/commons/ApiVariable";
|
||||
import MsApiBinaryVariable from "./ApiBinaryVariable";
|
||||
import MsApiFromUrlVariable from "./ApiFromUrlVariable";
|
||||
import BatchAddParameter from "metersphere-frontend/src/components/environment/commons/BatchAddParameter";
|
||||
|
||||
|
||||
export default {
|
||||
name: "MsApiBody",
|
||||
components: {
|
||||
MsApiVariable,
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsApiKeyValue,
|
||||
MsApiBinaryVariable,
|
||||
MsApiFromUrlVariable,
|
||||
BatchAddParameter
|
||||
},
|
||||
props: {
|
||||
body: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
json: true,
|
||||
kV: false,
|
||||
kvs: [],
|
||||
oldKV: false,
|
||||
type: "JSON",
|
||||
valid: false,
|
||||
xml: false
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: Array,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShowEnable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
scenarioDefinition: Array,
|
||||
id: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: BODY_TYPE,
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
jsonSchema: "JSON",
|
||||
codeEditActive: true,
|
||||
hasOwnProperty: Object.prototype.hasOwnProperty,
|
||||
propIsEnumerable: Object.prototype.propertyIsEnumerable,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
'body.typeChange'() {
|
||||
this.reloadCodeEdit();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isObj(x) {
|
||||
let type = typeof x;
|
||||
return x !== null && (type === 'object' || type === 'function');
|
||||
},
|
||||
|
||||
toObject(val) {
|
||||
if (val === null || val === undefined) {
|
||||
return;
|
||||
}
|
||||
return Object(val);
|
||||
},
|
||||
|
||||
assignKey(to, from, key) {
|
||||
let val = from[key];
|
||||
if (val === undefined || val === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.hasOwnProperty.call(to, key) || !this.isObj(val)) {
|
||||
to[key] = val;
|
||||
} else {
|
||||
to[key] = this.assign(Object(to[key]), from[key]);
|
||||
}
|
||||
},
|
||||
assign(to, from) {
|
||||
if (to === from) {
|
||||
return to;
|
||||
}
|
||||
from = Object(from);
|
||||
for (let key in from) {
|
||||
if (this.hasOwnProperty.call(from, key)) {
|
||||
this.assignKey(to, from, key);
|
||||
}
|
||||
}
|
||||
// 清除多出部分属性
|
||||
for (let key in to) {
|
||||
if (!this.hasOwnProperty.call(from, key) && key !== 'description') {
|
||||
delete to[key]
|
||||
}
|
||||
}
|
||||
if (Object.getOwnPropertySymbols) {
|
||||
let symbols = Object.getOwnPropertySymbols(from);
|
||||
for (let i = 0; i < symbols.length; i++) {
|
||||
if (this.propIsEnumerable.call(from, symbols[i])) {
|
||||
this.assignKey(to, from, symbols[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
},
|
||||
|
||||
deepAssign(target) {
|
||||
target = this.toObject(target);
|
||||
for (let s = 1; s < arguments.length; s++) {
|
||||
this.assign(target, arguments[s]);
|
||||
}
|
||||
return target;
|
||||
},
|
||||
reloadCodeEdit() {
|
||||
this.codeEditActive = false;
|
||||
this.$nextTick(() => {
|
||||
this.codeEditActive = true;
|
||||
});
|
||||
},
|
||||
modeChange(mode) {
|
||||
switch (this.body.type) {
|
||||
case "JSON":
|
||||
this.setContentType("application/json");
|
||||
break;
|
||||
case "XML":
|
||||
this.setContentType("text/xml");
|
||||
break;
|
||||
case "WWW_FORM":
|
||||
this.setContentType("application/x-www-form-urlencoded");
|
||||
break;
|
||||
// todo from data
|
||||
case "BINARY":
|
||||
this.setContentType("application/octet-stream");
|
||||
break;
|
||||
default:
|
||||
this.removeContentType();
|
||||
break;
|
||||
}
|
||||
},
|
||||
setContentType(value) {
|
||||
let isType = false;
|
||||
this.headers.forEach(item => {
|
||||
if (item.name === "Content-Type" || item.name == "contentType") {
|
||||
item.value = value;
|
||||
isType = true;
|
||||
}
|
||||
})
|
||||
if (this.body && this.body.kvs && value === "application/x-www-form-urlencoded") {
|
||||
this.body.kvs.forEach(item => {
|
||||
item.urlEncode = true;
|
||||
});
|
||||
}
|
||||
if (!isType) {
|
||||
this.headers.unshift(new KeyValue({name: "Content-Type", value: value}));
|
||||
this.$emit('headersChange');
|
||||
}
|
||||
},
|
||||
removeContentType() {
|
||||
for (let index in this.headers) {
|
||||
if (this.headers[index].name === "Content-Type") {
|
||||
this.headers.splice(index, 1);
|
||||
this.$emit('headersChange');
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
batchAdd() {
|
||||
this.$refs.batchAddParameter.open();
|
||||
},
|
||||
format(array, obj) {
|
||||
if (array) {
|
||||
let isAdd = true;
|
||||
for (let i in array) {
|
||||
let item = array[i];
|
||||
if (item.name === obj.name) {
|
||||
item.value = obj.value;
|
||||
isAdd = false;
|
||||
}
|
||||
}
|
||||
if (isAdd) {
|
||||
this.body.kvs.splice(this.body.kvs.indexOf(kv => !kv.name), 0, obj);
|
||||
}
|
||||
}
|
||||
},
|
||||
batchSave(data) {
|
||||
if (data) {
|
||||
let params = data.split("\n");
|
||||
let keyValues = [];
|
||||
params.forEach(item => {
|
||||
if (item) {
|
||||
let line = [];
|
||||
line[0] = item.substring(0, item.indexOf(":"));
|
||||
line[1] = item.substring(item.indexOf(":") + 1, item.length);
|
||||
let required = false;
|
||||
keyValues.push(new KeyValue({
|
||||
name: line[0],
|
||||
required: required,
|
||||
value: line[1],
|
||||
description: line[2],
|
||||
type: "text",
|
||||
valid: false,
|
||||
file: false,
|
||||
encode: true,
|
||||
enable: true,
|
||||
contentType: "text/plain"
|
||||
}));
|
||||
}
|
||||
})
|
||||
keyValues.forEach(item => {
|
||||
this.format(this.body.kvs, item);
|
||||
})
|
||||
}
|
||||
},
|
||||
editScenarioAdvance(data) {
|
||||
this.$emit('editScenarioAdvance', data);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (!this.body.type) {
|
||||
this.body.type = BODY_TYPE.FORM_DATA;
|
||||
}
|
||||
if (this.body.kvs) {
|
||||
this.body.kvs.forEach(param => {
|
||||
if (!param.type) {
|
||||
param.type = 'text';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.textarea {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ms-body {
|
||||
padding: 15px 0;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
margin-left: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.ace_editor {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.el-radio-group {
|
||||
margin: 10px 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.ms-el-link {
|
||||
float: right;
|
||||
margin-right: 45px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,222 @@
|
|||
<template>
|
||||
<div style="min-width: 1200px;margin-bottom: 20px">
|
||||
<span class="kv-description" v-if="description">
|
||||
{{ description }}
|
||||
</span>
|
||||
|
||||
<div class="kv-row" v-for="(item, index) in parameters" :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-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col class="kv-select">
|
||||
<el-select v-model="item.required" size="small">
|
||||
<el-option v-for="req in requireds" :key="req.id" :label="req.name" :value="req.id"/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="item.type !== 'file'">
|
||||
<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 pointer" @click="advanced(item)"></i>
|
||||
</el-autocomplete>
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<el-input v-model="item.description" size="small" maxlength="200"
|
||||
:placeholder="$t('commons.description')" show-word-limit>
|
||||
</el-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</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>
|
||||
<ms-api-variable-advance ref="variableAdvance" :environment="environment" :scenario="scenario"
|
||||
:parameters="parameters"
|
||||
:current-item="currentItem"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {KeyValue, Scenario} from "metersphere-frontend/src/model/ApiTestModel";
|
||||
import {JMETER_FUNC, MOCKJS_FUNC} from "metersphere-frontend/src/utils/constants";
|
||||
import MsApiVariableAdvance from "metersphere-frontend/src/components/environment/commons/ApiVariableAdvance";
|
||||
import MsApiBodyFileUpload from "metersphere-frontend/src/components/environment/commons/ApiBodyFileUpload";
|
||||
import {REQUIRED} from "metersphere-frontend/src/model/JsonData";
|
||||
|
||||
export default {
|
||||
name: "MsApiVariable",
|
||||
components: {MsApiBodyFileUpload, MsApiVariableAdvance},
|
||||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
description: String,
|
||||
parameters: Array,
|
||||
rest: Array,
|
||||
environment: Object,
|
||||
scenario: Scenario,
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
suggestions: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentItem: null,
|
||||
requireds: REQUIRED
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
keyText() {
|
||||
return this.keyPlaceholder || this.$t("api_test.key");
|
||||
},
|
||||
valueText() {
|
||||
return this.valuePlaceholder || this.$t("api_test.value");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove: function (index) {
|
||||
// 移除整行输入控件及内容
|
||||
this.parameters.splice(index, 1);
|
||||
this.$emit('change', this.parameters);
|
||||
},
|
||||
change: function () {
|
||||
let isNeedCreate = true;
|
||||
let removeIndex = -1;
|
||||
this.parameters.forEach((item, index) => {
|
||||
if (!item.name && !item.value) {
|
||||
// 多余的空行
|
||||
if (index !== this.parameters.length - 1) {
|
||||
removeIndex = index;
|
||||
}
|
||||
// 没有空行,需要创建空行
|
||||
isNeedCreate = false;
|
||||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'application/x-www-from-urlencoded'
|
||||
}));
|
||||
}
|
||||
this.$emit('change', this.parameters);
|
||||
// TODO 检查key重复
|
||||
},
|
||||
isDisable: function (index) {
|
||||
return this.parameters.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.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);
|
||||
};
|
||||
},
|
||||
uuid: function () {
|
||||
return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1);
|
||||
},
|
||||
advanced(item) {
|
||||
this.$refs.variableAdvance.open();
|
||||
this.currentItem = item;
|
||||
},
|
||||
typeChange(item) {
|
||||
item.contentType = 'application/x-www-from-urlencoded';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
|
||||
this.parameters.push(new KeyValue({type: 'text', enable: true, required: true, uuid: this.uuid(), contentType: 'application/x-www-from-urlencoded'}));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kv-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kv-delete {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.kv-select {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.el-autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kv-checkbox {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.advanced-item-value :deep(.el-dialog__body) {
|
||||
padding: 15px 25px;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.kv-type {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
color: #1E90FF;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,767 @@
|
|||
<template>
|
||||
<ms-container v-loading="loading">
|
||||
<ms-main-container class="api-report-content">
|
||||
<el-card class="report-body">
|
||||
<section class="report-container" v-if="this.report.testId">
|
||||
<!-- header -->
|
||||
<ms-api-report-view-header
|
||||
:show-cancel-button="showCancel"
|
||||
:show-rerun-button="showRerunButton"
|
||||
:is-plan="isPlan"
|
||||
:is-template="isTemplate"
|
||||
:debug="debug"
|
||||
:report="report"
|
||||
:project-env-map="projectEnvMap"
|
||||
@reportExport="handleExport"
|
||||
@reportSave="handleSave"/>
|
||||
|
||||
<!-- content -->
|
||||
<main v-if="isNotRunning">
|
||||
<!-- content header chart -->
|
||||
<ms-metric-chart :content="content" :totalTime="totalTime" :report="report"/>
|
||||
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick" style="min-width: 1000px">
|
||||
<!-- all step-->
|
||||
<el-tab-pane label="All" name="total">
|
||||
<ms-scenario-results
|
||||
:treeData="fullTreeNodes"
|
||||
:console="content.console"
|
||||
:report="report"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
v-on:requestResult="requestResult"
|
||||
ref="resultsTree"/>
|
||||
</el-tab-pane>
|
||||
<!-- fail step -->
|
||||
<el-tab-pane name="fail">
|
||||
<template slot="label">
|
||||
Error
|
||||
</template>
|
||||
<ms-scenario-results
|
||||
v-on:requestResult="requestResult"
|
||||
:console="content.console"
|
||||
:report="report"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
:treeData="fullTreeNodes" ref="failsTree"
|
||||
:errorReport="content.error"/>
|
||||
</el-tab-pane>
|
||||
<!--error step -->
|
||||
<el-tab-pane name="errorReport" v-if="content.errorCode > 0">
|
||||
<template slot="label">
|
||||
<span class="fail" style="color: #F6972A">
|
||||
FakeError
|
||||
</span>
|
||||
</template>
|
||||
<ms-scenario-results
|
||||
v-on:requestResult="requestResult"
|
||||
:report="report"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
:console="content.console"
|
||||
:treeData="fullTreeNodes" ref="errorReportTree"/>
|
||||
</el-tab-pane>
|
||||
<!-- Not performed step -->
|
||||
<el-tab-pane name="unExecute" v-if="content.unExecute > 0">
|
||||
<template slot="label">
|
||||
<span class="fail"
|
||||
style="color: #9C9B9A">
|
||||
Pending
|
||||
</span>
|
||||
</template>
|
||||
<ms-scenario-results
|
||||
v-on:requestResult="requestResult"
|
||||
:report="report"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
:console="content.console"
|
||||
:treeData="fullTreeNodes" ref="unExecuteTree"/>
|
||||
</el-tab-pane>
|
||||
<!-- console -->
|
||||
<el-tab-pane name="console">
|
||||
<template slot="label">
|
||||
<span class="console">Console</span>
|
||||
</template>
|
||||
<ms-code-edit
|
||||
:mode="'text'"
|
||||
:read-only="true"
|
||||
:data.sync="content.console"
|
||||
height="calc(100vh - 500px)"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!--export report-->
|
||||
<ms-api-report-export
|
||||
v-if="reportExportVisible"
|
||||
id="apiTestReport"
|
||||
:project-env-map="projectEnvMap"
|
||||
:title="report.name"
|
||||
:content="content"
|
||||
:report="report"
|
||||
:total-time="totalTime"/>
|
||||
</main>
|
||||
</section>
|
||||
</el-card>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import MsMetricChart from "../ui/MetricChart";
|
||||
import MsScenarioResults from "../ui/ScenarioResults";
|
||||
import MsContainer from "metersphere-frontend/src/components/MsContainer";
|
||||
import MsMainContainer from "metersphere-frontend/src/components/MsMainContainer";
|
||||
import MsApiReportViewHeader from "./ApiReportViewHeader";
|
||||
import {RequestFactory} from "metersphere-frontend/src/model/ApiTestModel";
|
||||
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token";
|
||||
import {getUUID, windowPrint} from "metersphere-frontend/src/utils";
|
||||
import {hasLicense} from "metersphere-frontend/src/utils/permission";
|
||||
import {STEP} from "metersphere-frontend/src/model/Setting";
|
||||
import MsCodeEdit from "metersphere-frontend/src/components/MsCodeEdit";
|
||||
|
||||
export default {
|
||||
name: "MsApiReport",
|
||||
components: {
|
||||
MsApiReportViewHeader,
|
||||
MsMainContainer,
|
||||
MsCodeEdit,
|
||||
MsContainer,
|
||||
MsScenarioResults,
|
||||
MsMetricChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeName: "total",
|
||||
content: {},
|
||||
report: {},
|
||||
loading: true,
|
||||
fails: [],
|
||||
failsTreeNodes: [],
|
||||
totalTime: 0,
|
||||
isRequestResult: false,
|
||||
request: {},
|
||||
isActive: false,
|
||||
scenarioName: null,
|
||||
reportExportVisible: false,
|
||||
requestType: undefined,
|
||||
fullTreeNodes: [],
|
||||
showRerunButton: false,
|
||||
stepFilter: new STEP,
|
||||
exportReportIsOk: false,
|
||||
tempResult: [],
|
||||
projectEnvMap: {},
|
||||
showCancel: false
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.isRequestResult = false;
|
||||
},
|
||||
props: {
|
||||
reportId: String,
|
||||
currentProjectId: String,
|
||||
infoDb: Boolean,
|
||||
debug: Boolean,
|
||||
isTemplate: Boolean,
|
||||
templateReport: Object,
|
||||
isShare: Boolean,
|
||||
shareId: String,
|
||||
isPlan: Boolean,
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
reportId() {
|
||||
if (!this.isTemplate) {
|
||||
this.getReport();
|
||||
}
|
||||
},
|
||||
templateReport() {
|
||||
if (this.isTemplate) {
|
||||
this.getReport();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filter(index) {
|
||||
if (index === "1") {
|
||||
this.$refs.failsTree.filter(index);
|
||||
} else if (this.activeName === "errorReport") {
|
||||
this.$refs.errorReportTree.filter("FAKE_ERROR");
|
||||
} else if (this.activeName === "unExecute") {
|
||||
this.$refs.unExecuteTree.filter("PENDING");
|
||||
}
|
||||
},
|
||||
init() {
|
||||
this.loading = true;
|
||||
this.projectEnvMap = {};
|
||||
this.content = {};
|
||||
this.fails = [];
|
||||
this.report = {};
|
||||
this.fullTreeNodes = [];
|
||||
this.failsTreeNodes = [];
|
||||
this.isRequestResult = false;
|
||||
this.activeName = "total";
|
||||
this.showRerunButton = false;
|
||||
if (this.$route && this.$route.path.startsWith('/api/automation/report')
|
||||
&& this.$route.query && this.$route.query.list) {
|
||||
this.$nextTick(() => {
|
||||
this.showCancel = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
rerunVerify() {
|
||||
if (hasLicense() && this.fullTreeNodes && this.fullTreeNodes.length > 0 && !this.isShare) {
|
||||
this.fullTreeNodes.forEach(item => {
|
||||
item.redirect = true;
|
||||
if (item.totalStatus === 'FAIL' || item.totalStatus === 'ERROR' || item.unExecuteTotal > 0
|
||||
|| (item.type === "API" && item.totalStatus === 'PENDING')) {
|
||||
this.showRerunButton = true;
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
handleClick(tab, event) {
|
||||
this.isRequestResult = false;
|
||||
if (this.report && this.report.reportVersion && this.report.reportVersion > 1) {
|
||||
this.filter(tab.index);
|
||||
}
|
||||
},
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
formatResult(res) {
|
||||
let resMap = new Map;
|
||||
let array = [];
|
||||
if (res && res.scenarios) {
|
||||
res.scenarios.forEach(item => {
|
||||
if (item && item.requestResults) {
|
||||
item.requestResults.forEach(req => {
|
||||
req.responseResult.console = res.console;
|
||||
resMap.set(req.id + req.name, req);
|
||||
req.name = item.name + "^@~@^" + req.name + "UUID=" + getUUID();
|
||||
array.push(req);
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
this.formatTree(array, this.fullTreeNodes);
|
||||
this.sort(this.fullTreeNodes);
|
||||
this.$emit('refresh', resMap);
|
||||
},
|
||||
formatTree(array, tree) {
|
||||
array.map((item) => {
|
||||
let key = item.name;
|
||||
let nodeArray = key.split('^@~@^');
|
||||
let children = tree;
|
||||
//运行场景中如果连续将1个场景引入多次,会出现运行结果合并的情况。
|
||||
//为了解决这种问题,在转hashTree的时候给场景放了个新ID,前台加载解析的时候也要做处理
|
||||
let scenarioId = "";
|
||||
let scenarioName = "";
|
||||
if (item.scenario) {
|
||||
let scenarioArr = JSON.parse(item.scenario);
|
||||
if (scenarioArr.length > 1) {
|
||||
let scenarioIdArr = scenarioArr[0].split("_");
|
||||
scenarioId = scenarioIdArr[0];
|
||||
scenarioName = scenarioIdArr[1];
|
||||
}
|
||||
}
|
||||
// 循环构建子节点
|
||||
for (let i = 0; i < nodeArray.length; i++) {
|
||||
if (!nodeArray[i]) {
|
||||
continue;
|
||||
}
|
||||
let node = {
|
||||
label: nodeArray[i],
|
||||
value: item,
|
||||
};
|
||||
if (i !== (nodeArray.length - 1)) {
|
||||
node.children = [];
|
||||
} else {
|
||||
if (item.subRequestResults && item.subRequestResults.length > 0) {
|
||||
let itemChildren = this.deepFormatTreeNode(item.subRequestResults);
|
||||
node.children = itemChildren;
|
||||
if (node.label.indexOf("UUID=")) {
|
||||
node.label = node.label.split("UUID=")[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (children.length === 0) {
|
||||
children.push(node);
|
||||
}
|
||||
|
||||
let isExist = false;
|
||||
for (let j in children) {
|
||||
if (children[j].label === node.label) {
|
||||
|
||||
let idIsPath = true;
|
||||
//判断ID是否匹配 目前发现问题的只有重复场景,而重复场景是在第二个节点开始合并的。所以这里暂时只判断第二个场景问题。
|
||||
//如果出现了其他问题,则需要检查其他问题的数据结构。暂时采用具体问题具体分析的策略
|
||||
if (i === nodeArray.length - 2) {
|
||||
idIsPath = false;
|
||||
let childId = "";
|
||||
let childName = "";
|
||||
if (children[j].value && children[j].value.scenario) {
|
||||
let scenarioArr = JSON.parse(children[j].value.scenario);
|
||||
if (scenarioArr.length > 1) {
|
||||
let childArr = scenarioArr[0].split("_");
|
||||
childId = childArr[0];
|
||||
if (childArr.length > 1) {
|
||||
childName = childArr[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scenarioId === "") {
|
||||
idIsPath = true;
|
||||
} else if (scenarioId === childId) {
|
||||
idIsPath = true;
|
||||
} else if (scenarioName !== childName) {
|
||||
//如果两个名字不匹配则默认通过,不匹配ID
|
||||
idIsPath = true;
|
||||
}
|
||||
}
|
||||
if (idIsPath) {
|
||||
if (i !== nodeArray.length - 1 && !children[j].children) {
|
||||
children[j].children = [];
|
||||
}
|
||||
children = (i === nodeArray.length - 1 ? children : children[j].children);
|
||||
isExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isExist) {
|
||||
children.push(node);
|
||||
if (i !== nodeArray.length - 1 && !children[children.length - 1].children) {
|
||||
children[children.length - 1].children = [];
|
||||
}
|
||||
children = (i === nodeArray.length - 1 ? children : children[children.length - 1].children);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
deepFormatTreeNode(array) {
|
||||
let returnChildren = [];
|
||||
array.map((item) => {
|
||||
let children = [];
|
||||
let key = item.name.split('^@~@^')[0];
|
||||
let nodeArray = key.split('<->');
|
||||
//运行场景中如果连续将1个场景引入多次,会出现运行结果合并的情况。
|
||||
//为了解决这种问题,在转hashTree的时候给场景放了个新ID,前台加载解析的时候也要做处理
|
||||
let scenarioId = "";
|
||||
let scenarioName = "";
|
||||
if (item.scenario) {
|
||||
let scenarioArr = JSON.parse(item.scenario);
|
||||
if (scenarioArr.length > 1) {
|
||||
let scenarioIdArr = scenarioArr[0].split("_");
|
||||
scenarioId = scenarioIdArr[0];
|
||||
scenarioName = scenarioIdArr[1];
|
||||
}
|
||||
}
|
||||
// 循环构建子节点
|
||||
let node = {
|
||||
label: nodeArray[0],
|
||||
value: item,
|
||||
children: []
|
||||
};
|
||||
if (item.subRequestResults && item.subRequestResults.length > 0) {
|
||||
let itemChildren = this.deepFormatTreeNode(item.subRequestResults);
|
||||
node.children = itemChildren;
|
||||
}
|
||||
children.push(node);
|
||||
children.forEach(itemNode => {
|
||||
returnChildren.push(itemNode);
|
||||
});
|
||||
|
||||
});
|
||||
return returnChildren;
|
||||
},
|
||||
recursiveSorting(arr) {
|
||||
for (let i in arr) {
|
||||
if (arr[i]) {
|
||||
arr[i].index = Number(i) + 1;
|
||||
if (arr[i].children && arr[i].children.length > 0) {
|
||||
this.recursiveSorting(arr[i].children);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sort(scenarioDefinition) {
|
||||
for (let i in scenarioDefinition) {
|
||||
// 排序
|
||||
if (scenarioDefinition[i]) {
|
||||
scenarioDefinition[i].index = Number(i) + 1;
|
||||
if (scenarioDefinition[i].children && scenarioDefinition[i].children.length > 0) {
|
||||
this.recursiveSorting(scenarioDefinition[i].children);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getReportByExport() {
|
||||
if (this.exportReportIsOk) {
|
||||
this.startExport();
|
||||
} else {
|
||||
getScenarioReportDetail(this.reportId).then((res) => {
|
||||
let data = res.data;
|
||||
if (data && data.content) {
|
||||
let report = JSON.parse(data.content);
|
||||
if (report.projectEnvMap) {
|
||||
this.projectEnvMap = report.projectEnvMap;
|
||||
}
|
||||
this.content = report;
|
||||
this.fullTreeNodes = report.steps;
|
||||
this.content.console = report.console;
|
||||
this.content.error = report.error;
|
||||
let successCount = (report.total - report.error - report.errorCode - report.unExecute);
|
||||
this.content.success = successCount;
|
||||
this.totalTime = report.totalTime;
|
||||
}
|
||||
this.exportReportIsOk = true;
|
||||
setTimeout(this.startExport, 500)
|
||||
});
|
||||
}
|
||||
},
|
||||
getReport() {
|
||||
this.init();
|
||||
if (this.isTemplate) {
|
||||
// 测试计划报告导出
|
||||
if (this.templateReport) {
|
||||
this.handleGetScenarioReport(this.templateReport);
|
||||
} else {
|
||||
this.report = this.templateReport;
|
||||
this.buildReport();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
checkReport(data) {
|
||||
if (!data) {
|
||||
this.$emit('reportNotExist');
|
||||
}
|
||||
},
|
||||
handleGetScenarioReport(data) {
|
||||
if (data) {
|
||||
this.report = data;
|
||||
if (this.report.reportVersion && this.report.reportVersion > 1) {
|
||||
this.report.status = data.status;
|
||||
if (!this.isNotRunning) {
|
||||
setTimeout(this.getReport, 2000)
|
||||
} else {
|
||||
if (data.content) {
|
||||
let report = JSON.parse(data.content);
|
||||
this.content = report;
|
||||
if (report.projectEnvMap) {
|
||||
this.projectEnvMap = report.projectEnvMap;
|
||||
}
|
||||
this.fullTreeNodes = report.steps;
|
||||
this.content.console = report.console;
|
||||
this.content.error = report.error;
|
||||
let successCount = (report.total - report.error - report.errorCode - report.unExecute);
|
||||
this.content.success = successCount;
|
||||
this.totalTime = report.totalTime;
|
||||
}
|
||||
// 增加失败重跑校验
|
||||
if (this.report && this.report.reportType === 'SCENARIO_INTEGRATED' || this.report.reportType === 'API_INTEGRATED') {
|
||||
this.rerunVerify();
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
} else {
|
||||
this.buildReport();
|
||||
}
|
||||
} else {
|
||||
this.$emit('invisible');
|
||||
this.$warning(this.$t('commons.report_delete'));
|
||||
}
|
||||
},
|
||||
checkOrder(origin) {
|
||||
if (!origin) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(origin)) {
|
||||
this.sortChildren(origin);
|
||||
origin.forEach(v => {
|
||||
if (v.children) {
|
||||
this.checkOrder(v.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
sortChildren(source) {
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
source.forEach(item => {
|
||||
let children = item.children;
|
||||
if (children && children.length > 0) {
|
||||
let tempArr = new Array(children.length);
|
||||
let tempMap = new Map();
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (!children[i].value || !children[i].value.startTime || children[i].value.startTime === 0) {
|
||||
//若没有value或未执行的,则step留在当前位置
|
||||
tempArr[i] = children[i];
|
||||
//进行标识
|
||||
tempMap.set(children[i].stepId, children[i])
|
||||
}
|
||||
}
|
||||
|
||||
//过滤出还没有指定好位置的step
|
||||
let arr = children.filter(m => {
|
||||
return !tempMap.get(m.stepId);
|
||||
}).sort((m, n) => {
|
||||
//按时间排序
|
||||
return m.value.startTime - n.value.startTime;
|
||||
});
|
||||
|
||||
//找出arr(已经有序,从头取即可)中时间最小的插入 tempArr 可用位置
|
||||
for (let j = 0, i = 0; j < tempArr.length; j++) {
|
||||
if (!tempArr[j]) {
|
||||
//占位
|
||||
tempArr[j] = arr[i];
|
||||
i++;
|
||||
}
|
||||
//重新排序
|
||||
tempArr[j].index = j + 1;
|
||||
}
|
||||
|
||||
//赋值
|
||||
item.children = tempArr;
|
||||
}
|
||||
})
|
||||
},
|
||||
buildReport() {
|
||||
if (this.report) {
|
||||
if (this.isNotRunning) {
|
||||
this.content = JSON.parse(this.report.content);
|
||||
if (!this.content) {
|
||||
this.content = {scenarios: []};
|
||||
}
|
||||
this.formatResult(this.content);
|
||||
this.getFails();
|
||||
this.computeTotalTime();
|
||||
this.loading = false;
|
||||
} else {
|
||||
setTimeout(this.getReport, 2000)
|
||||
}
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.$error(this.$t('api_report.not_exist'));
|
||||
}
|
||||
},
|
||||
getFails() {
|
||||
if (this.isNotRunning) {
|
||||
this.fails = [];
|
||||
let array = [];
|
||||
this.totalTime = 0
|
||||
if (this.content.scenarios) {
|
||||
this.content.scenarios.forEach((scenario) => {
|
||||
this.totalTime = this.totalTime + Number(scenario.responseTime)
|
||||
let failScenario = Object.assign({}, scenario);
|
||||
if (scenario.error > 0) {
|
||||
this.fails.push(failScenario);
|
||||
failScenario.requestResults = [];
|
||||
scenario.requestResults.forEach((request) => {
|
||||
if (!request.success) {
|
||||
let failRequest = Object.assign({}, request);
|
||||
failScenario.requestResults.push(failRequest);
|
||||
array.push(request);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
this.formatTree(array, this.failsTreeNodes);
|
||||
this.sort(this.failsTreeNodes);
|
||||
}
|
||||
},
|
||||
computeTotalTime() {
|
||||
if (this.content.scenarios) {
|
||||
let startTime = 0;
|
||||
let endTime = 0;
|
||||
let requestTime = 0;
|
||||
this.content.scenarios.forEach((scenario) => {
|
||||
scenario.requestResults.forEach((request) => {
|
||||
if (request.startTime && Number(request.startTime)) {
|
||||
startTime = request.startTime;
|
||||
}
|
||||
if (request.endTime && Number(request.endTime)) {
|
||||
endTime = request.endTime;
|
||||
}
|
||||
let resTime;
|
||||
if (startTime === 0 || endTime === 0) {
|
||||
resTime = 0
|
||||
} else {
|
||||
resTime = endTime - startTime
|
||||
}
|
||||
requestTime = requestTime + resTime;
|
||||
})
|
||||
})
|
||||
this.totalTime = requestTime
|
||||
}
|
||||
},
|
||||
requestResult(requestResult) {
|
||||
this.active();
|
||||
this.isRequestResult = false;
|
||||
this.requestType = undefined;
|
||||
if (requestResult.request.body.indexOf('[Callable Statement]') > -1) {
|
||||
this.requestType = RequestFactory.TYPES.SQL;
|
||||
}
|
||||
this.$nextTick(function () {
|
||||
this.isRequestResult = true;
|
||||
this.request = requestResult.request;
|
||||
this.scenarioName = requestResult.scenarioName;
|
||||
});
|
||||
},
|
||||
formatExportApi(array, scenario) {
|
||||
array.forEach(item => {
|
||||
if (this.stepFilter && this.stepFilter.get("AllSamplerProxy").indexOf(item.type) !== -1) {
|
||||
if (item.errorCode) {
|
||||
item.value.errorCode = item.errorCode;
|
||||
}
|
||||
scenario.requestResults.push(item.value);
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
this.formatExportApi(item.children, scenario);
|
||||
}
|
||||
})
|
||||
},
|
||||
handleExport() {
|
||||
this.getReportByExport();
|
||||
},
|
||||
startExport() {
|
||||
if (this.report.reportVersion && this.report.reportVersion > 1) {
|
||||
if (this.report.reportType === 'API_INTEGRATED' || this.report.reportType === 'UI_INTEGRATED') {
|
||||
let scenario = {name: "", requestResults: []};
|
||||
this.content.scenarios = [scenario];
|
||||
this.formatExportApi(this.fullTreeNodes, scenario);
|
||||
} else {
|
||||
if (this.fullTreeNodes) {
|
||||
this.fullTreeNodes.forEach(item => {
|
||||
if (item.type === "scenario" || item.type === "UiScenario") {
|
||||
let scenario = {name: item.label, requestResults: []};
|
||||
if (this.content.scenarios && this.content.scenarios.length > 0) {
|
||||
this.content.scenarios.push(scenario);
|
||||
} else {
|
||||
this.content.scenarios = [scenario];
|
||||
}
|
||||
this.formatExportApi(item.children, scenario);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
this.reportExportVisible = true;
|
||||
let reset = this.exportReportReset;
|
||||
this.$nextTick(() => {
|
||||
windowPrint('apiTestReport', 0.57);
|
||||
reset();
|
||||
});
|
||||
},
|
||||
handleSave() {
|
||||
if (!this.report.name) {
|
||||
this.$warning(this.$t('api_test.automation.report_name_info'));
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
reportReName({
|
||||
id: this.report.id,
|
||||
name: this.report.name,
|
||||
reportType: this.report.reportType
|
||||
}).then(response => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.loading = false;
|
||||
this.$emit('refresh');
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
exportReportReset() {
|
||||
this.$router.go(0);
|
||||
},
|
||||
handleProjectChange() {
|
||||
this.$router.push('/api/automation/report');
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.showCancel = this.showCancelButton;
|
||||
this.getReport();
|
||||
if (this.$EventBus) {
|
||||
this.$EventBus.$on('projectChange', this.handleProjectChange);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
if (this.$EventBus) {
|
||||
this.$EventBus.$off('projectChange', this.handleProjectChange);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
path() {
|
||||
return "/api/test/edit?id=" + this.report.testId;
|
||||
},
|
||||
isNotRunning() {
|
||||
return "RUNNING" !== this.report.status;
|
||||
},
|
||||
projectId() {
|
||||
return getCurrentProjectID();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.report-container .el-tabs__header {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.report-container {
|
||||
height: calc(100vh - 155px);
|
||||
min-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.report-header {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.report-header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.report-header .time {
|
||||
color: #909399;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.report-container .fail {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.report-container .is-active .fail {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.report-console {
|
||||
height: calc(100vh - 270px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.export-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.scenario-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.report-body {
|
||||
min-width: 750px !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,452 @@
|
|||
<template>
|
||||
<ms-container>
|
||||
<ms-main-container>
|
||||
<el-card class="table-card" v-loading="result">
|
||||
<template v-slot:header>
|
||||
<ms-table-header :condition.sync="condition" v-if="loadIsOver" @search="search" :show-create="false">
|
||||
<template v-slot:button>
|
||||
<el-button-group>
|
||||
|
||||
<el-tooltip class="item" effect="dark" content="left" :disabled="true" placement="left">
|
||||
<el-button plain :class="{active: leftActive}" @click="changeTab('left')">
|
||||
{{ $t('commons.scenario') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip class="item" effect="dark" content="right" :disabled="true" placement="right">
|
||||
<el-button plain :class="{active: rightActive}" @click="changeTab('right')">
|
||||
{{ $t('api_test.definition.request.case') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
</el-button-group>
|
||||
</template>
|
||||
</ms-table-header>
|
||||
</template>
|
||||
|
||||
|
||||
<el-table ref="reportListTable" border :data="tableData" class="adjust-table table-content" @sort-change="sort"
|
||||
@select-all="handleSelectAll"
|
||||
@select="handleSelect"
|
||||
:height="screenHeight"
|
||||
@filter-change="filter" @row-click="handleView" v-if="loadIsOver">
|
||||
<el-table-column
|
||||
type="selection"/>
|
||||
<el-table-column width="40" :resizable="false" align="center">
|
||||
<el-dropdown slot="header" style="width: 14px">
|
||||
<span class="el-dropdown-link" style="width: 14px">
|
||||
<i class="el-icon-arrow-down el-icon--right" style="margin-left: 0px"></i>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native.stop="isSelectDataAll(true)">
|
||||
{{ $t('api_test.batch_menus.select_all_data', [total]) }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click.native.stop="isSelectDataAll(false)">
|
||||
{{ $t('api_test.batch_menus.select_show_data', [tableData.length]) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<template v-slot:default="scope">
|
||||
<show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectDataCounts"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<ms-table-column
|
||||
prop="name"
|
||||
sortable
|
||||
:label="$t('commons.name')"
|
||||
:show-overflow-tooltip="false"
|
||||
:editable="true"
|
||||
:edit-content="$t('report.rename_report')"
|
||||
@editColumn="openReNameDialog"
|
||||
min-width="200px">
|
||||
</ms-table-column>
|
||||
|
||||
<el-table-column prop="reportType" :label="$t('load_test.report_type')" width="150"
|
||||
column-key="reportType"
|
||||
:filters="reportTypeFilters">
|
||||
<template v-slot:default="scope">
|
||||
<div v-if="scope.row.reportType === 'SCENARIO_INTEGRATED'">
|
||||
<el-tag size="mini" type="primary">
|
||||
{{ $t('api_test.scenario.integrated') }}
|
||||
</el-tag>
|
||||
{{ $t('commons.scenario') }}
|
||||
</div>
|
||||
<div v-else-if="scope.row.reportType === 'API_INDEPENDENT'">
|
||||
<el-tag size="mini" type="primary">
|
||||
{{ $t('api_test.scenario.independent') }}
|
||||
</el-tag>
|
||||
case
|
||||
</div>
|
||||
<div v-else-if="scope.row.reportType === 'API_INTEGRATED'">
|
||||
<el-tag size="mini" type="primary">
|
||||
{{ $t('api_test.scenario.integrated') }}
|
||||
</el-tag>
|
||||
case
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tag size="mini" type="primary">
|
||||
{{ $t('api_test.scenario.independent') }}
|
||||
</el-tag>
|
||||
{{ $t('commons.scenario') }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<ms-table-column prop="userName" :label="$t('api_test.creator')" width="150" show-overflow-tooltip
|
||||
:filters="userFilters"/>
|
||||
<el-table-column prop="createTime" min-width="120" :label="$t('commons.create_time')" sortable>
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.createTime | datetimeFormat }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" min-width="120" :label="$t('report.test_end_time')" sortable>
|
||||
<template v-slot:default="scope">
|
||||
<span v-if="scope.row.endTime && scope.row.endTime > 0">
|
||||
{{ scope.row.endTime | datetimeFormat }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ scope.row.updateTime | datetimeFormat }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="triggerMode" width="150" :label="$t('commons.trigger_mode.name')"
|
||||
column-key="triggerMode" :filters="triggerFilters">
|
||||
<template v-slot:default="scope">
|
||||
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('commons.status')"
|
||||
:filters="statusFilters"
|
||||
column-key="status"
|
||||
prop="status">
|
||||
<template v-slot:default="{row}">
|
||||
<ms-api-report-status :status="row.status"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="150" :label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<div>
|
||||
<ms-table-operator-button
|
||||
:tip="$t('api_report.detail')" icon="el-icon-s-data"
|
||||
@exec="handleView(scope.row)" type="primary"/>
|
||||
<ms-table-operator-button
|
||||
:tip="$t('api_report.delete')"
|
||||
v-permission="['PROJECT_API_REPORT:READ+DELETE']"
|
||||
icon="el-icon-delete" @exec="handleDelete(scope.row)" type="danger"/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||
:total="total"/>
|
||||
</el-card>
|
||||
<ms-rename-report-dialog ref="renameDialog" @submit="rename($event)"></ms-rename-report-dialog>
|
||||
<el-dialog :close-on-click-modal="false" :title="$t('test_track.plan_view.test_result')" width="60%"
|
||||
:visible.sync="resVisible" class="api-import" destroy-on-close @close="resVisible=false">
|
||||
<ms-request-result-tail :report-id="reportId" :response="response" ref="debugResult"/>
|
||||
</el-dialog>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
</template>
|
||||
<script>
|
||||
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token";
|
||||
import {REPORT_CASE_CONFIGS, REPORT_CONFIGS} from "metersphere-frontend/src/components/search/search-components";
|
||||
import {_filter, _sort} from "metersphere-frontend/src/utils/tableUtils";
|
||||
import MsRenameReportDialog from "metersphere-frontend/src/components/report/MsRenameReportDialog";
|
||||
import MsTableColumn from "metersphere-frontend/src/components/table/MsTableColumn";
|
||||
import MsRequestResultTail from "@/business/definition/components/response/RequestResultTail";
|
||||
import MsTabButton from "@/business/commons/MsTabs";
|
||||
import {getMaintainer} from "@/api/project";
|
||||
import {delBatchReport, delReport, getReportPage, reportReName} from "@/api/scenario-report";
|
||||
import {getApiReportPage} from "@/api/definition-report";
|
||||
import {REPORT_STATUS} from "@/business/commons/js/commons";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ReportTriggerModeItem: () => import("metersphere-frontend/src/components/tableItem/ReportTriggerModeItem"),
|
||||
MsTableOperatorButton: () => import("metersphere-frontend/src/components/MsTableOperatorButton"),
|
||||
MsApiReportStatus: () => import("./ApiReportStatus"),
|
||||
MsMainContainer: () => import("metersphere-frontend/src/components/MsMainContainer"),
|
||||
MsContainer: () => import("metersphere-frontend/src/components/MsContainer"),
|
||||
MsTableHeader: () => import("metersphere-frontend/src/components/MsTableHeader"),
|
||||
MsTablePagination: () => import("metersphere-frontend/src/components/pagination/TablePagination"),
|
||||
ShowMoreBtn: () => import("@/business/commons/ShowMoreBtn"),
|
||||
MsRenameReportDialog,
|
||||
MsTableColumn,
|
||||
MsTabButton,
|
||||
MsRequestResultTail,
|
||||
},
|
||||
props: {
|
||||
reportType: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: false,
|
||||
resVisible: false,
|
||||
response: {},
|
||||
reportId: "",
|
||||
debugVisible: false,
|
||||
condition: {
|
||||
components: REPORT_CONFIGS
|
||||
},
|
||||
tableData: [],
|
||||
multipleSelection: [],
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
loadIsOver: true,
|
||||
total: 0,
|
||||
loading: false,
|
||||
currentProjectId: "",
|
||||
statusFilters: REPORT_STATUS,
|
||||
reportTypeFilters: [],
|
||||
reportScenarioFilters: [
|
||||
{text: this.$t('api_test.scenario.independent') + this.$t('commons.scenario'), value: 'SCENARIO_INDEPENDENT'},
|
||||
{text: this.$t('api_test.scenario.integrated') + this.$t('commons.scenario'), value: 'SCENARIO_INTEGRATED'}
|
||||
],
|
||||
reportCaseFilters: [
|
||||
{text: this.$t('api_test.scenario.independent') + 'case', value: 'API_INDEPENDENT'},
|
||||
{text: this.$t('api_test.scenario.integrated') + 'case', value: 'API_INTEGRATED'},
|
||||
],
|
||||
triggerFilters: [
|
||||
{text: this.$t('commons.trigger_mode.manual'), value: 'MANUAL'},
|
||||
{text: this.$t('commons.trigger_mode.schedule'), value: 'SCHEDULE'},
|
||||
{text: this.$t('commons.trigger_mode.api'), value: 'API'},
|
||||
{text: this.$t('api_test.automation.batch_execute'), value: 'BATCH'},
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
name: this.$t('api_report.batch_delete'),
|
||||
handleClick: this.handleBatchDelete,
|
||||
permissions: ['PROJECT_API_REPORT:READ+DELETE']
|
||||
}
|
||||
],
|
||||
selectRows: new Set(),
|
||||
selectAll: false,
|
||||
unSelection: [],
|
||||
selectDataCounts: 0,
|
||||
screenHeight: 'calc(100vh - 160px)',
|
||||
trashActiveDom: 'left',
|
||||
userFilters: [],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route'(to, from) {
|
||||
if (to.path.startsWith('/api/automation/report')) {
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
trashActiveDom() {
|
||||
this.condition.filters = {report_type: []};
|
||||
this.search();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
leftActive() {
|
||||
return this.trashActiveDom === 'left';
|
||||
},
|
||||
rightActive() {
|
||||
return this.trashActiveDom === 'right';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getMaintainerOptions() {
|
||||
getMaintainer().then(response => {
|
||||
this.userFilters = response.data.map(u => {
|
||||
return {text: u.name, value: u.id};
|
||||
});
|
||||
});
|
||||
},
|
||||
search() {
|
||||
if (this.testId !== 'all') {
|
||||
this.condition.testId = this.testId;
|
||||
}
|
||||
this.condition.projectId = getCurrentProjectID();
|
||||
this.selectAll = false;
|
||||
this.unSelection = [];
|
||||
this.selectDataCounts = 0;
|
||||
|
||||
this.condition.reportType = this.reportType;
|
||||
if (this.condition.orders && this.condition.orders.length > 0) {
|
||||
let order = this.condition.orders[this.condition.orders.length - 1];
|
||||
this.condition.orders = [];
|
||||
this.condition.orders.push(order);
|
||||
}
|
||||
if (this.trashActiveDom === 'left') {
|
||||
this.reportTypeFilters = this.reportScenarioFilters;
|
||||
this.result = getReportPage(this.currentPage, this.pageSize, this.condition).then(res => {
|
||||
this.setData(res);
|
||||
})
|
||||
} else {
|
||||
this.reportTypeFilters = this.reportCaseFilters;
|
||||
this.result = getApiReportPage(this.currentPage, this.pageSize, this.condition).then(res => {
|
||||
this.setData(res);
|
||||
})
|
||||
}
|
||||
},
|
||||
setData(response) {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
this.selectRows.clear();
|
||||
this.unSelection = data.listObject.map(s => s.id);
|
||||
},
|
||||
handleView(report) {
|
||||
this.reportId = report.id;
|
||||
if (report.status === 'RUNNING' || report.status === 'RERUNNING') {
|
||||
this.$warning(this.$t('commons.run_warning'))
|
||||
return;
|
||||
}
|
||||
if (report.reportType.indexOf('SCENARIO') !== -1 || report.reportType === 'API_INTEGRATED') {
|
||||
this.currentProjectId = report.projectId;
|
||||
this.$router.push({
|
||||
path: 'report/view/' + report.id,
|
||||
query: {list: true}
|
||||
});
|
||||
} else {
|
||||
this.resVisible = true;
|
||||
}
|
||||
},
|
||||
handleDelete(report) {
|
||||
this.$alert(this.$t('api_report.delete_confirm') + report.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
delReport(report.id).then(() => {
|
||||
this.$success(this.$t('commons.delete_success'));
|
||||
this.search();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.testId = this.$route.params.testId;
|
||||
this.search();
|
||||
},
|
||||
sort(column) {
|
||||
_sort(column, this.condition);
|
||||
this.init();
|
||||
},
|
||||
filter(filters) {
|
||||
_filter(filters, this.condition);
|
||||
this.init();
|
||||
},
|
||||
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);
|
||||
}
|
||||
this.selectRowsCount(this.selectRows)
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
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);
|
||||
})
|
||||
}
|
||||
this.selectRowsCount(this.selectRows)
|
||||
},
|
||||
handleBatchDelete() {
|
||||
this.$alert(this.$t('api_report.delete_batch_confirm') + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
let ids = Array.from(this.selectRows).map(row => row.id);
|
||||
let sendParam = {};
|
||||
sendParam.ids = ids;
|
||||
sendParam.selectAllDate = this.isSelectAllDate;
|
||||
sendParam.unSelectIds = this.unSelection;
|
||||
sendParam = Object.assign(sendParam, this.condition);
|
||||
sendParam.caseType = this.trashActiveDom === 'right' ? 'API' : 'SCENARIO';
|
||||
delBatchReport(sendParam).then(() => {
|
||||
this.selectRows.clear();
|
||||
this.$success(this.$t('commons.delete_success'));
|
||||
this.search();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
selectRowsCount(selection) {
|
||||
let selectedIDs = this.getIds(selection);
|
||||
let allIDs = this.tableData.map(s => s.id);
|
||||
this.unSelection = allIDs.filter(function (val) {
|
||||
return selectedIDs.indexOf(val) === -1
|
||||
});
|
||||
if (this.isSelectAllDate) {
|
||||
this.selectDataCounts = this.total - this.unSelection.length;
|
||||
} else {
|
||||
this.selectDataCounts = selection.size;
|
||||
}
|
||||
},
|
||||
isSelectDataAll(dataType) {
|
||||
this.isSelectAllDate = dataType;
|
||||
this.selectRowsCount(this.selectRows)
|
||||
//如果已经全选,不需要再操作了
|
||||
if (this.selectRows.size != this.tableData.length) {
|
||||
this.$refs.reportListTable.toggleAllSelection(true);
|
||||
}
|
||||
},
|
||||
getIds(rowSets) {
|
||||
let rowArray = Array.from(rowSets)
|
||||
let ids = rowArray.map(s => s.id);
|
||||
return ids;
|
||||
},
|
||||
openReNameDialog($event) {
|
||||
this.$refs.renameDialog.open($event);
|
||||
},
|
||||
rename(data) {
|
||||
reportReName(data).then(() => {
|
||||
this.$success(this.$t("organization.integration.successful_operation"));
|
||||
this.init();
|
||||
this.$refs.renameDialog.close();
|
||||
});
|
||||
},
|
||||
changeTab(tabType) {
|
||||
this.trashActiveDom = tabType;
|
||||
if (tabType === 'right') {
|
||||
this.condition.components = REPORT_CASE_CONFIGS;
|
||||
} else {
|
||||
this.condition.components = REPORT_CONFIGS;
|
||||
}
|
||||
this.loadIsOver = false;
|
||||
this.$nextTick(() => {
|
||||
this.loadIsOver = true;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.init();
|
||||
this.getMaintainerOptions();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.active {
|
||||
border: solid 1px #6d317c !important;
|
||||
background-color: var(--primary_color) !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 32px;
|
||||
padding: 5px 8px;
|
||||
border: solid 1px var(--primary_color);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div class="item">
|
||||
<div class="item-title">
|
||||
{{title}}
|
||||
</div>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ApiReportRequestHeaderItem",
|
||||
props: {title: String}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.item {
|
||||
width: 120px;
|
||||
height: 50px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-tag size="mini" type="primary" effect="plain" v-if="getStatus(status) === 'running'">
|
||||
{{ showStatus(status) }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="success" v-else-if="getStatus(status) === 'success'">
|
||||
{{ showStatus(status) }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else-if="getStatus(status) === 'error'">
|
||||
{{ showStatus(status) }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" style="background-color: #F6972A; color: #FFFFFF"
|
||||
v-else-if="getStatus(status) === 'fake_error'">
|
||||
FakeError
|
||||
</el-tag>
|
||||
<span v-else-if="status === '-'" size="mini" type="info">
|
||||
-
|
||||
</span>
|
||||
<el-tag v-else size="mini" type="info">
|
||||
{{ showStatus(status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsApiReportStatus",
|
||||
|
||||
props: {
|
||||
status: String
|
||||
},
|
||||
methods: {
|
||||
getStatus(status) {
|
||||
if (status) {
|
||||
return status.toLowerCase();
|
||||
}
|
||||
return "PENDING";
|
||||
},
|
||||
showStatus(status) {
|
||||
if (!status) {
|
||||
status = 'PENDING';
|
||||
}
|
||||
return status.toLowerCase()[0].toUpperCase() + status.toLowerCase().substr(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<header class="report-header">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<span v-if="!debug">
|
||||
<span>
|
||||
<el-link v-if="isSingleScenario"
|
||||
type="primary"
|
||||
class="report-name"
|
||||
@click="redirect">
|
||||
{{ report.name }}
|
||||
</el-link>
|
||||
<span v-else>
|
||||
{{ report.name }}
|
||||
</span>
|
||||
<i v-if="showCancelButton" class="el-icon-edit" style="cursor:pointer" @click="nameIsEdit = true"
|
||||
@click.stop/>
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="report.endTime || report.createTime">
|
||||
<span style="margin-left: 10px">{{ $t('report.test_start_time') }}:</span>
|
||||
<span class="time"> {{ report.createTime | datetimeFormat }}</span>
|
||||
<span style="margin-left: 10px">{{ $t('report.test_end_time') }}:</span>
|
||||
<span class="time"> {{ report.endTime | datetimeFormat }}</span>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="showProjectEnv" type="flex">
|
||||
<span> {{ $t('commons.environment') + ':' }} </span>
|
||||
<div v-for="(values,key) in projectEnvMap" :key="key" style="margin-right: 10px">
|
||||
{{ key + ":" }}
|
||||
<ms-tag v-for="(item,index) in values" :key="index" type="success" :content="item"
|
||||
style="margin-left: 2px"/>
|
||||
</div>
|
||||
</el-row>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {getCurrentProjectID, getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
|
||||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||
import {getUUID} from "metersphere-frontend/src/utils";
|
||||
|
||||
export default {
|
||||
name: "MsApiReportViewHeader",
|
||||
components: {MsTag},
|
||||
props: {
|
||||
report: {},
|
||||
projectEnvMap: {},
|
||||
debug: Boolean,
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showRerunButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isTemplate: Boolean,
|
||||
exportFlag: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isPlan: Boolean
|
||||
},
|
||||
computed: {
|
||||
showProjectEnv() {
|
||||
return this.projectEnvMap && JSON.stringify(this.projectEnvMap) !== '{}';
|
||||
},
|
||||
path() {
|
||||
return "/api/test/edit?id=" + this.report.testId;
|
||||
},
|
||||
scenarioId() {
|
||||
if (typeof this.report.scenarioId === 'string') {
|
||||
return this.report.scenarioId;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
isSingleScenario() {
|
||||
try {
|
||||
JSON.parse(this.report.scenarioId);
|
||||
return false;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isReadOnly: false,
|
||||
nameIsEdit: false,
|
||||
shareUrl: "",
|
||||
application: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSaveKeyUp($event) {
|
||||
$event.target.blur();
|
||||
},
|
||||
redirect() {
|
||||
let uuid = getUUID().substring(1, 5);
|
||||
let projectId = getCurrentProjectID();
|
||||
let workspaceId = getCurrentWorkspaceId();
|
||||
let path = `/api/automation/?redirectID=${uuid}&dataType=scenario&projectId=${projectId}&workspaceId=${workspaceId}&resourceId=${this.scenarioId}`;
|
||||
let data = this.$router.resolve({
|
||||
path: path
|
||||
});
|
||||
window.open(data.href, '_blank');
|
||||
},
|
||||
returnView() {
|
||||
this.$router.push('/api/automation/report');
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.export-button {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.rerun-button {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
background-color: #F2F9EF;
|
||||
color: #87C45D;
|
||||
}
|
||||
|
||||
.report-name {
|
||||
border-bottom: 1px solid var(--primary_color);
|
||||
}
|
||||
|
||||
.report-header {
|
||||
min-width: 1000px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<div class="metric-container">
|
||||
<el-row type="flex">
|
||||
<el-col>
|
||||
<div style="font-size: 14px;color: #AAAAAA;float: left">{{ $t('api_report.response_code') }} :</div>
|
||||
<el-tooltip
|
||||
v-if="responseResult.responseCode"
|
||||
:content="responseResult.responseCode"
|
||||
placement="top">
|
||||
|
||||
<div
|
||||
v-if="response.attachInfoMap && response.attachInfoMap.FAKE_ERROR
|
||||
&& response.attachInfoMap.status === 'FAKE_ERROR'" class="node-title" :class="'ms-req-error-report-result'">
|
||||
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
|
||||
</div>
|
||||
<div v-else class="node-title" :class="response && response.success ?'ms-req-success':'ms-req-error'">
|
||||
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<div v-else class="node-title" :class="response && response.success ?'ms-req-success':'ms-req-error'">
|
||||
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
|
||||
</div>
|
||||
<div v-if="response && response.attachInfoMap && response.attachInfoMap.FAKE_ERROR">
|
||||
<div class="node-title ms-req-error-report-result"
|
||||
v-if="response.attachInfoMap.status === 'FAKE_ERROR'" style="margin-left: 0px;padding-left: 0px">
|
||||
{{ response.attachInfoMap.FAKE_ERROR }}
|
||||
</div>
|
||||
<div class="node-title ms-req-success" v-else-if="response.success"
|
||||
style="margin-left: 0px;padding-left: 0px">
|
||||
{{ response.attachInfoMap.FAKE_ERROR }}
|
||||
</div>
|
||||
<div class="node-title ms-req-error" v-else style="margin-left: 0px;padding-left: 0px">
|
||||
{{ response.attachInfoMap.FAKE_ERROR }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<div style="font-size: 14px;color: #AAAAAA;float: left">{{ $t('api_report.response_time') }} :</div>
|
||||
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">
|
||||
{{ responseResult && responseResult.responseTime ? responseResult.responseTime : 0 }} ms
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<div style="font-size: 14px;color: #AAAAAA;float: left">{{ $t('api_report.response_size') }} :</div>
|
||||
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">
|
||||
{{ responseResult && responseResult.responseSize ? responseResult.responseSize : 0 }} bytes
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" v-if="response && response.envName">
|
||||
<div style="font-size: 14px;color: #AAAAAA;float: left">
|
||||
<span> {{ $t('commons.environment') + ':' }} </span>
|
||||
</div>
|
||||
<div style="font-size: 14px;color:#61C550; margin-left:10px;float: left">
|
||||
{{ response.envName }}
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsRequestMetric",
|
||||
|
||||
props: {
|
||||
response: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
responseResult() {
|
||||
return this.response && this.response.responseResult ? this.response.responseResult : {};
|
||||
},
|
||||
error() {
|
||||
return this.response && this.response.responseCode && this.response.responseCode >= 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.metric-container {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
/*width: 150px;*/
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
padding: 0px 5px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
color: #61C550;
|
||||
margin-top: 2px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
float: left
|
||||
}
|
||||
|
||||
.ms-req-error {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.ms-req-error-report-result {
|
||||
color: #F6972A;
|
||||
}
|
||||
|
||||
.ms-req-success {
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,353 @@
|
|||
<template>
|
||||
<el-card class="ms-cards" v-if="request && request.responseResult">
|
||||
<div class="request-result">
|
||||
<div @click="active">
|
||||
<el-row :gutter="18" type="flex" align="middle" class="info">
|
||||
<el-col class="ms-req-name-col" :span="18" v-if="indexNumber!=undefined">
|
||||
<el-tooltip :content="getName(request.name)" placement="top">
|
||||
<span class="method ms-req-name">
|
||||
<div class="el-step__icon is-text ms-api-col-create">
|
||||
<div class="el-step__icon-inner"> {{ indexNumber }}</div>
|
||||
</div>
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': showActive}" @click="active" @click.stop/>
|
||||
<span class="report-label-req" @click="isLink" v-if="redirect && resourceId">
|
||||
{{ request.name }}
|
||||
</span>
|
||||
<span v-else>{{ getName(request.name) }}</span>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<!-- 误报 / 异常状态显示 -->
|
||||
<el-col :span="3">
|
||||
<el-tooltip effect="dark" v-if="baseErrorCode && baseErrorCode !==''" :content="baseErrorCode"
|
||||
style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom"
|
||||
:open-delay="800">
|
||||
<div :style="{color: statusColor(totalStatus ? totalStatus : request.status)}">
|
||||
{{ baseErrorCode }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<!-- 请求返回状态 -->
|
||||
<el-col :span="6">
|
||||
<el-tooltip effect="dark" :content="request.responseResult.responseCode"
|
||||
style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom"
|
||||
:open-delay="800">
|
||||
<div :style="{color: statusColor(totalStatus ? totalStatus : request.status)}">
|
||||
{{ request.responseResult.responseCode }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<!-- 请求响应时间 -->
|
||||
<el-col :span="3">
|
||||
<div :style="{color: statusColor(totalStatus ? totalStatus : request.status)}">
|
||||
{{ request.responseResult.responseTime }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-tag v-if="request.testing" class="ms-test-running" size="mini">
|
||||
<i class="el-icon-loading" style="font-size: 16px"/>
|
||||
{{ $t('commons.testing') }}
|
||||
</el-tag>
|
||||
<ms-api-report-status :status="totalStatus || request.status" v-else/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-collapse-transition>
|
||||
<div v-show="showActive && !request.unexecute" style="width: 99%">
|
||||
<ms-request-result-tail
|
||||
v-loading="requestInfo.loading"
|
||||
:scenario-name="scenarioName"
|
||||
:request-type="requestType"
|
||||
:request="requestInfo"
|
||||
:console="console"
|
||||
v-if="showActive"/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsRequestMetric from "./RequestMetric";
|
||||
import MsAssertionResults from "../ui/AssertionResults";
|
||||
import MsRequestText from "./RequestText";
|
||||
import MsResponseText from "./ResponseText";
|
||||
import MsRequestResultTail from "./RequestResultTail";
|
||||
import MsApiReportStatus from "./ApiReportStatus";
|
||||
|
||||
export default {
|
||||
name: "MsRequestResult",
|
||||
components: {
|
||||
MsApiReportStatus,
|
||||
MsResponseText,
|
||||
MsRequestText,
|
||||
MsAssertionResults,
|
||||
MsRequestMetric,
|
||||
MsRequestResultTail
|
||||
},
|
||||
props: {
|
||||
request: Object,
|
||||
resourceId: String,
|
||||
scenarioName: String,
|
||||
stepId: String,
|
||||
indexNumber: Number,
|
||||
console: String,
|
||||
totalStatus: String,
|
||||
redirect: Boolean,
|
||||
errorCode: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShare: Boolean,
|
||||
shareId: String,
|
||||
},
|
||||
created() {
|
||||
this.showActive = this.isActive;
|
||||
this.baseErrorCode = this.errorCode;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
requestType: "",
|
||||
color: {
|
||||
type: String,
|
||||
default() {
|
||||
return "#B8741A";
|
||||
}
|
||||
},
|
||||
requestInfo: {
|
||||
loading: true,
|
||||
hasData: false,
|
||||
responseResult: {},
|
||||
subRequestResults: [],
|
||||
},
|
||||
baseErrorCode: "",
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default() {
|
||||
return "#F9F1EA";
|
||||
}
|
||||
},
|
||||
showActive: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isActive() {
|
||||
this.loadRequestInfoExpand();
|
||||
this.showActive = this.isActive;
|
||||
},
|
||||
errorCode() {
|
||||
this.baseErrorCode = this.errorCode;
|
||||
},
|
||||
request: {
|
||||
deep: true,
|
||||
handler(n) {
|
||||
if (this.request.errorCode) {
|
||||
this.baseErrorCode = this.request.errorCode;
|
||||
} else if (this.request.attachInfoMap && this.request.attachInfoMap.FAKE_ERROR) {
|
||||
if (this.request.attachInfoMap.FAKE_ERROR !== "") {
|
||||
this.baseErrorCode = this.request.attachInfoMap.FAKE_ERROR;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
statusColor(status) {
|
||||
return this.getReportStatusColor(status);
|
||||
},
|
||||
getReportStatusColor(status) {
|
||||
if (status) {
|
||||
status = status.toUpperCase();
|
||||
}
|
||||
if (status === 'SUCCESS') {
|
||||
return '#5daf34';
|
||||
} else if (status === 'FAKE_ERROR') {
|
||||
return '#F6972A';
|
||||
} else if (status === 'ERROR') {
|
||||
return '#FE6F71';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
isLink() {
|
||||
let uri = "/#/api/definition?caseId=" + this.resourceId;
|
||||
this.clickResource(uri)
|
||||
},
|
||||
toPage(uri) {
|
||||
let id = "new_a";
|
||||
let a = document.createElement("a");
|
||||
a.setAttribute("href", uri);
|
||||
a.setAttribute("target", "_blank");
|
||||
a.setAttribute("id", id);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
let element = document.getElementById(id);
|
||||
element.parentNode.removeChild(element);
|
||||
},
|
||||
loadRequestInfoExpand() {
|
||||
this.requestInfo = this.request;
|
||||
},
|
||||
active() {
|
||||
if (this.request.unexecute) {
|
||||
this.showActive = false;
|
||||
} else {
|
||||
this.showActive = !this.showActive;
|
||||
}
|
||||
if (this.showActive) {
|
||||
this.loadRequestInfoExpand();
|
||||
}
|
||||
},
|
||||
getName(name) {
|
||||
if (name && name.indexOf("<->") !== -1) {
|
||||
return name.split("<->")[0];
|
||||
}
|
||||
if (name && name.indexOf("^@~@^") !== -1) {
|
||||
let arr = name.split("^@~@^");
|
||||
let value = arr[arr.length - 1];
|
||||
if (value.indexOf("UUID=") !== -1) {
|
||||
return value.split("UUID=")[0];
|
||||
}
|
||||
if (value && value.startsWith("UUID=")) {
|
||||
return "";
|
||||
}
|
||||
if (value && value.indexOf("<->") !== -1) {
|
||||
return value.split("<->")[0];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (name && name.startsWith("UUID=")) {
|
||||
return "";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
min-height: 30px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 35px;
|
||||
padding-left: 5px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1E90FF;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.ms-cards :deep(.el-card__body) {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.ms-test-running {
|
||||
color: #783887;
|
||||
}
|
||||
|
||||
.ms-test-error_code {
|
||||
color: #F6972A;
|
||||
background-color: #FDF5EA;
|
||||
border-color: #FDF5EA;
|
||||
}
|
||||
|
||||
.ms-api-col {
|
||||
background-color: #EFF0F0;
|
||||
border-color: #EFF0F0;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #64666A;
|
||||
}
|
||||
|
||||
.ms-api-col-create {
|
||||
background-color: #EBF2F2;
|
||||
border-color: #008080;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
:deep(.el-step__icon) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: 2px 0;
|
||||
background: 0 0;
|
||||
border-top: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.ms-req-name {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
padding-bottom: 0;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.ms-req-name-col {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.report-label-req {
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #303133;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<div class="request-result" v-loading="loading">
|
||||
<ms-request-metric v-if="showMetric" :response="response"/>
|
||||
<ms-response-result :currentProtocol="currentProtocol" :response="response"
|
||||
:isTestPlan="isTestPlan"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsResponseResult from "./ResponseResult";
|
||||
import MsRequestMetric from "./RequestMetric";
|
||||
|
||||
export default {
|
||||
name: "MsRequestResultTail",
|
||||
components: {MsRequestMetric, MsResponseResult},
|
||||
props: {
|
||||
response: Object,
|
||||
currentProtocol: String,
|
||||
reportId: String,
|
||||
showMetric: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
isTestPlan: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
reportId: {
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
report: {},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
background-color: #F9F9F9;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1E90FF;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.request-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div class="text-container">
|
||||
<div @click="active" class="collapse">
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
||||
{{$t('api_report.request')}}
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane label="Body" name="body" class="pane">
|
||||
<pre>{{request.body}}</pre>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Headers" name="headers" class="pane">
|
||||
<pre>{{request.headers}}</pre>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Cookies" name="cookies" class="pane">
|
||||
<pre>{{request.cookies}}</pre>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsRequestText",
|
||||
|
||||
props: {
|
||||
request: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-container .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.text-container .collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-container .collapse:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.text-container .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.text-container .pane {
|
||||
background-color: #F9F9F9;
|
||||
padding: 1px 0;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,197 @@
|
|||
<template>
|
||||
<div class="text-container" v-if="responseResult">
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane :label="$t('api_test.definition.request.response_body')" name="body" class="pane">
|
||||
<ms-sql-result-table v-if="isSqlType && activeName === 'body'" :body="responseResult.body"/>
|
||||
<ms-code-edit v-if="!isSqlType && isMsCodeEditShow && activeName === 'body'" :mode="mode" :read-only="true"
|
||||
:modes="modes" :data.sync="responseResult.body" ref="codeEdit"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane">
|
||||
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="responseResult.headers"
|
||||
v-if="activeName === 'headers'"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="isTestPlan" :label="$t('api_test.definition.request.console')" name="console" class="pane">
|
||||
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="responseResult.console"
|
||||
v-if="activeName === 'console'" height="calc(100vh - 300px)"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="!isTestPlan" :label="$t('api_test.definition.request.console')" name="console" class="pane">
|
||||
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="responseResult.console"
|
||||
v-if="activeName === 'console'"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
|
||||
<ms-assertion-results :assertions="responseResult.assertions" v-if="activeName === 'assertions'"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
|
||||
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="responseResult.vars" v-if="activeName === 'label'"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_report.request_body')" name="request_body" class="pane">
|
||||
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="reqMessages" v-if="activeName === 'request_body'"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane cookie">
|
||||
<template v-slot:label>
|
||||
<ms-dropdown v-if="currentProtocol==='SQL'" :commands="sqlModes" :default-command="mode" @command="sqlModeChange"/>
|
||||
<ms-dropdown v-else :commands="modes" :default-command="mode" @command="modeChange" ref="modeDropdown"/>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsAssertionResults from "../ui/AssertionResults";
|
||||
import MsCodeEdit from "metersphere-frontend/src/components/MsCodeEdit";
|
||||
import MsDropdown from "metersphere-frontend/src/components/MsDropdown";
|
||||
import {BODY_FORMAT} from "metersphere-frontend/src/model/ApiTestModel";
|
||||
import MsSqlResultTable from "./SqlResultTable";
|
||||
|
||||
export default {
|
||||
name: "MsResponseResult",
|
||||
|
||||
components: {
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
MsSqlResultTable
|
||||
},
|
||||
|
||||
props: {
|
||||
response: Object,
|
||||
currentProtocol: String,
|
||||
isTestPlan: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
sqlModes: ['text', 'table'],
|
||||
mode: BODY_FORMAT.TEXT,
|
||||
isMsCodeEditShow: true,
|
||||
reqMessages: "",
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
response() {
|
||||
this.setBodyType();
|
||||
this.setReqMessage();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
modeChange(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
sqlModeChange(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
setBodyType() {
|
||||
if (this.response && this.response.responseResult && this.response.responseResult.headers
|
||||
&& this.response.responseResult.headers.indexOf("Content-Type: application/json") > 0) {
|
||||
this.mode = BODY_FORMAT.JSON;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.modeDropdown) {
|
||||
this.$refs.modeDropdown.handleCommand(BODY_FORMAT.JSON);
|
||||
this.msCodeReload();
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
msCodeReload() {
|
||||
this.isMsCodeEditShow = false;
|
||||
this.$nextTick(() => {
|
||||
this.isMsCodeEditShow = true;
|
||||
});
|
||||
},
|
||||
setReqMessage() {
|
||||
if (this.response) {
|
||||
if (!this.response.url) {
|
||||
this.response.url = "";
|
||||
}
|
||||
if (!this.response.headers) {
|
||||
this.response.headers = "";
|
||||
}
|
||||
if (!this.response.cookies) {
|
||||
this.response.cookies = "";
|
||||
}
|
||||
if (!this.response.body) {
|
||||
this.response.body = "";
|
||||
}
|
||||
if (!this.response.responseResult) {
|
||||
this.response.responseResult = {};
|
||||
}
|
||||
if (!this.response.responseResult.vars) {
|
||||
this.response.responseResult.vars = "";
|
||||
}
|
||||
this.reqMessages = this.$t('api_test.request.address') + ":\n" + this.response.url + "\n" +
|
||||
this.$t('api_test.scenario.headers') + ":\n" + this.response.headers + "\n" + "Cookies :\n" +
|
||||
this.response.cookies + "\n" + "Body:" + "\n" + this.response.body;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setBodyType();
|
||||
this.setReqMessage();
|
||||
},
|
||||
computed: {
|
||||
isSqlType() {
|
||||
return (this.currentProtocol === "SQL" && this.response.responseResult.responseCode === '200' && this.mode === 'table');
|
||||
},
|
||||
responseResult() {
|
||||
return this.response && this.response.responseResult ? this.response.responseResult : {};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-container .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.text-container .collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-container .collapse:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.text-container .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.text-container .pane {
|
||||
background-color: #F5F5F5;
|
||||
padding: 1px 0;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-container .pane.cookie {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.ms-div {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<div class="text-container" style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100%">
|
||||
<el-form :model="response" ref="response" label-width="100px">
|
||||
|
||||
<el-collapse-transition>
|
||||
<el-tabs v-model="activeName" v-show="isActive" style="margin: 20px">
|
||||
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane">
|
||||
<ms-api-key-value :isShowEnable="false" :suggestions="headerSuggestions" :items="response.headers"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.definition.request.response_body')" name="body" class="pane">
|
||||
<ms-api-body :isReadOnly="false" :isShowEnable="false" :body="response.body" :headers="response.headers"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.definition.request.status_code')" name="status_code" class="pane" >
|
||||
<ms-api-key-value :isShowEnable="false" :suggestions="headerSuggestions"
|
||||
:items="response.statusCode" :unShowSelect = "true"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsAssertionResults from "../ui/AssertionResults";
|
||||
import MsCodeEdit from "metersphere-frontend/src/components/MsCodeEdit";
|
||||
import MsDropdown from "metersphere-frontend/src/components/MsDropdown";
|
||||
import MsApiKeyValue from "metersphere-frontend/src/components/environment/commons/ApiKeyValue";
|
||||
import {REQUEST_HEADERS} from "metersphere-frontend/src/utils/constants";
|
||||
import MsApiBody from "./ApiBody";
|
||||
|
||||
export default {
|
||||
name: "MsResponseText",
|
||||
components: {
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
MsApiKeyValue,
|
||||
MsApiBody,
|
||||
},
|
||||
|
||||
props: {
|
||||
response: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "headers",
|
||||
headerSuggestions: REQUEST_HEADERS
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (!this.response.headers) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-container .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.text-container .collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-container .collapse:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.text-container .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.text-container .pane {
|
||||
background-color: white;
|
||||
padding: 1px 0;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-container .pane.cookie {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
v-for="(table, index) in tables"
|
||||
:key="index"
|
||||
:data="table.tableData"
|
||||
border
|
||||
size="mini"
|
||||
highlight-current-row>
|
||||
<el-table-column v-for="(title, index) in table.titles" :key="index" :label="title" min-width="150px">
|
||||
<template v-slot:default="scope">
|
||||
<el-popover
|
||||
placement="top"
|
||||
trigger="click">
|
||||
<el-container>
|
||||
<div>{{ scope.row[title] }}</div>
|
||||
</el-container>
|
||||
<span class="table-content" slot="reference">{{ scope.row[title] }}</span>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsSqlResultTable",
|
||||
data() {
|
||||
return {
|
||||
tables: [],
|
||||
titles: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
body: String
|
||||
},
|
||||
created() {
|
||||
if (!this.body) {
|
||||
return;
|
||||
}
|
||||
let rowArray = this.body.split("\n");
|
||||
// 过多会有性能问题
|
||||
if (rowArray.length > 100) {
|
||||
rowArray = rowArray.slice(0, 100);
|
||||
}
|
||||
this.getTableData(rowArray);
|
||||
},
|
||||
methods: {
|
||||
getTableData(rowArray) {
|
||||
let titles;
|
||||
let result = [];
|
||||
for (let i = 0; i < rowArray.length; i++) {
|
||||
let colArray = rowArray[i].split("\t");
|
||||
if (i === 0) {
|
||||
titles = colArray;
|
||||
} else {
|
||||
if (colArray.length != titles.length) {
|
||||
// 创建新的表
|
||||
if (colArray.length === 1 && colArray[0] === '') {
|
||||
this.getTableData(rowArray.slice(i + 1));
|
||||
} else {
|
||||
this.getTableData(rowArray.slice(i));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
let item = {};
|
||||
for (let j = 0; j < colArray.length; j++) {
|
||||
item[titles[j]] = (colArray[j] ? colArray[j] : "");
|
||||
}
|
||||
// 性能考虑每个表格取值不超过一百
|
||||
if (result.length < 100) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tables.splice(0, 0, {
|
||||
titles: titles,
|
||||
tableData: result
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.el-table {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-table :deep(.cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-container {
|
||||
overflow: auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -71,8 +71,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
// 测试计划报告导出的是时候需要引入
|
||||
// import MsRequestResultTail from "../../../../../../../../../../api-test/frontend/src/business/definition/components/response/RequestResultTail";
|
||||
import MsRequestResultTail from "../api/RequestResultTail";
|
||||
|
||||
import PriorityTableItem from "../../../../../../common/tableItems/planview/PriorityTableItem";
|
||||
import TypeTableItem from "../../../../../../common/tableItems/planview/TypeTableItem";
|
||||
|
@ -105,7 +104,7 @@ export default {
|
|||
MsAsideContainer,
|
||||
MicroApp,
|
||||
MsTableColumn, MsTable, StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem,
|
||||
// MsRequestResultTail
|
||||
MsRequestResultTail
|
||||
},
|
||||
props: {
|
||||
planId: String,
|
||||
|
|
|
@ -80,8 +80,7 @@
|
|||
|
||||
<script>
|
||||
|
||||
// 测试计划报告导出的是时候需要引入
|
||||
// import MsApiReport from "../../../../../../../../../../api-test/frontend/src/business/automation/report/ApiReportDetail";
|
||||
import MsApiReport from "../api/ApiReportDetail";
|
||||
|
||||
import PriorityTableItem from "../../../../../../common/tableItems/planview/PriorityTableItem";
|
||||
import TypeTableItem from "../../../../../../common/tableItems/planview/TypeTableItem";
|
||||
|
@ -112,7 +111,7 @@ export default {
|
|||
MsAsideContainer,
|
||||
MicroApp,
|
||||
MsTableColumn, MsTable, StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem,
|
||||
// MsApiReport
|
||||
MsApiReport
|
||||
},
|
||||
props: {
|
||||
planId: String,
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsCodeEdit from "metersphere-frontend/src/components/MsCodeEdit";
|
||||
import MsDropdown from "metersphere-frontend/src/components/MsDropdown";
|
||||
import {BODY_FORMAT} from "metersphere-frontend/src/model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsResponseText",
|
||||
|
|
Loading…
Reference in New Issue