API报告页面

This commit is contained in:
q4speed 2020-05-09 14:51:37 +08:00
parent ae288e8ace
commit 2a7be35de1
20 changed files with 899 additions and 96 deletions

View File

@ -43,9 +43,9 @@ public class APIReportController {
return PageUtils.setPageInfo(page, apiReportService.list(request));
}
@GetMapping("/get/{testId}")
public ApiTestReport get(@PathVariable String testId) {
return apiReportService.get(testId);
@GetMapping("/get/{reportId}")
public APIReportResult get(@PathVariable String reportId) {
return apiReportService.get(reportId);
}
@PostMapping("/delete")

View File

@ -9,5 +9,7 @@ import lombok.Setter;
@Getter
public class APIReportResult extends ApiTestReport {
private String testName;
private String projectName;
}

View File

@ -93,7 +93,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
}
RequestResult requestResult = getRequestResult(result);
scenarioResult.getRequestResult().add(requestResult);
scenarioResult.getRequestResults().add(requestResult);
scenarioResult.addResponseTime(result.getTime());
testResult.addPassAssertions(requestResult.getPassAssertions());
testResult.addTotalAssertions(requestResult.getTotalAssertions());
@ -110,14 +111,18 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
}
private RequestResult getRequestResult(SampleResult result) {
String body = result.getSamplerData();
String method = StringUtils.substringBefore(body, " ");
RequestResult requestResult = new RequestResult();
requestResult.setName(result.getSampleLabel());
requestResult.setUrl(result.getUrlAsString());
requestResult.setSuccess(result.isSuccessful());
requestResult.setBody(result.getSamplerData());
requestResult.setMethod(method);
requestResult.setBody(body);
requestResult.setHeaders(result.getRequestHeaders());
requestResult.setRequestSize(result.getSentBytes());
requestResult.setTotalAssertions(result.getAssertionResults().length);
requestResult.setSuccess(result.isSuccessful());
ResponseResult responseResult = requestResult.getResponseResult();
responseResult.setBody(result.getResponseDataAsString());

View File

@ -9,6 +9,8 @@ public class RequestResult {
private String url;
private String method;
private long requestSize;
private boolean success;

View File

@ -22,7 +22,11 @@ public class ScenarioResult {
private int passAssertions = 0;
private final List<RequestResult> requestResult = new ArrayList<>();
private final List<RequestResult> requestResults = new ArrayList<>();
public void addResponseTime(long time) {
this.responseTime += time;
}
public void addError() {
this.error++;

View File

@ -38,8 +38,8 @@ public class APIReportService {
return extApiTestReportMapper.list(request);
}
public ApiTestReport get(String id) {
return apiTestReportMapper.selectByPrimaryKey(id);
public APIReportResult get(String reportId) {
return extApiTestReportMapper.get(reportId);
}
public List<APIReportResult> listByTestId(String testId) {

View File

@ -13,4 +13,6 @@ public interface ExtApiTestReportMapper {
List<APIReportResult> listByTestId(@Param("testId") String testId);
APIReportResult get(@Param("id") String id);
}

View File

@ -4,12 +4,13 @@
<resultMap id="BaseResultMap" type="io.metersphere.api.dto.APIReportResult"
extends="io.metersphere.base.mapper.ApiTestReportMapper.BaseResultMap">
<result column="test_name" property="testName"/>
<result column="project_name" property="projectName"/>
</resultMap>
<select id="list" resultMap="BaseResultMap">
SELECT t.name, t.description,
r.id, r.test_id, r.create_time, r.update_time, r.status,
SELECT t.name AS test_name,
r.name, r.description, r.id, r.test_id, r.create_time, r.update_time, r.status,
project.name AS project_name
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
JOIN project ON project.id = t.project_id
@ -30,8 +31,8 @@
</select>
<select id="listByTestId" resultMap="BaseResultMap">
SELECT t.name, t.description,
r.id, r.test_id, r.create_time, r.update_time, r.status,
SELECT c,
r.name, r.description, r.id, r.test_id, r.create_time, r.update_time, r.status,
project.name AS project_name
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
JOIN project ON project.id = t.project_id
@ -41,4 +42,14 @@
ORDER BY r.update_time DESC
</select>
<select id="get" resultMap="BaseResultMap">
SELECT r.*, t.name AS test_name, project.name AS project_name
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
JOIN project ON project.id = t.project_id
<where>
r.id = #{id}
</where>
ORDER BY r.update_time DESC
</select>
</mapper>

View File

@ -23,8 +23,8 @@
<el-divider/>
<ms-show-all :index="'/api/test/list/all'"/>
<ms-create-button :index="'/api/test/create'" :title="$t('load_test.create')"/>
<!-- <el-menu-item :index="testCaseProjectPath" class="blank_item"></el-menu-item>-->
<!-- <el-menu-item :index="testEditPath" class="blank_item"></el-menu-item>-->
<!-- <el-menu-item :index="testCaseProjectPath" class="blank_item"></el-menu-item>-->
<!-- <el-menu-item :index="testEditPath" class="blank_item"></el-menu-item>-->
</el-submenu>
<el-submenu v-if="isCurrentWorkspaceUser"
@ -33,7 +33,7 @@
<ms-recent-list :options="reportRecent"/>
<el-divider/>
<ms-show-all :index="'/api/report/list/all'"/>
<!-- <el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item>-->
<!-- <el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item>-->
</el-submenu>
</el-menu>
</el-col>
@ -90,7 +90,7 @@
title: this.$t('report.recent'),
url: "/api/report/recent/5",
index: function (item) {
return '/api/report/view?id=' + item.id;
return '/api/report/view/' + item.id;
}
}
}

View File

@ -32,6 +32,38 @@
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
prop="status"
:label="$t('commons.status')">
<template v-slot:default="{row}">
<el-tag size="mini" type="info" v-if="row.status === 'Saved'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="primary" v-else-if="row.status === 'Starting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<span v-else>
{{ row.status }}
</span>
</template>
</el-table-column>
<el-table-column
width="150"
:label="$t('commons.operating')">
@ -101,7 +133,7 @@
})
},
handleDelete(report) {
this.$alert(this.$t('load_test.delete_confirm') + report.name + "", '', {
this.$alert(this.$t('api_report.delete_confirm') + report.name + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {

View File

@ -1,117 +1,113 @@
<template>
<div v-loading="result.loading" class="report-view-container">
<div class="container" v-loading="result.loading">
<div class="main-content">
<el-card>
<el-row>
<el-col :span="16">
<el-row>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">{{report.projectName}}</el-breadcrumb-item>
<el-breadcrumb-item>{{report.testName}}</el-breadcrumb-item>
<el-breadcrumb-item>{{report.name}}</el-breadcrumb-item>
</el-breadcrumb>
</el-row>
<el-row class="ms-report-view-btns">
<el-button type="primary" plain size="mini">立即停止</el-button>
<el-button type="success" plain size="mini">再次执行</el-button>
<el-button type="info" plain size="mini">导出</el-button>
<el-button type="warning" plain size="mini">比较</el-button>
</el-row>
</el-col>
<el-col :span="8">
<span class="ms-report-time-desc">
持续时间 30 分钟
</span>
<span class="ms-report-time-desc">
开始时间 2020-3-10 12:00:00
</span>
<span class="ms-report-time-desc">
结束时间 2020-3-10 12:30:00
</span>
</el-col>
</el-row>
<el-divider></el-divider>
<el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('report.test_details')">
<result-details />
</el-tab-pane>
<el-tab-pane :label="$t('report.test_log_details')">
<ms-report-log-details />
</el-tab-pane>
</el-tabs>
<section class="report-container">
<header class="report-header" v-if="this.report.testId">
<span>{{report.projectName}} / </span>
<router-link :to="path">{{report.testName}}</router-link>
</header>
<main>
<div class="scenario-chart">
<ms-metric-chart :content="content"></ms-metric-chart>
</div>
<el-card>
<div class="scenario-header">
<el-row :gutter="10">
<el-col :span="16">
{{$t('api_report.scenario_name')}}
</el-col>
<el-col :span="2">
{{$t('api_report.response_time')}}
</el-col>
<el-col :span="2">
{{$t('api_report.error')}}
</el-col>
<el-col :span="2">
{{$t('api_report.assertions')}}
</el-col>
<el-col :span="2">
{{$t('api_report.result')}}
</el-col>
</el-row>
</div>
<ms-scenario-result v-for="(scenario, index) in content.scenarios" :key="index" :scenario="scenario"/>
</el-card>
</main>
</section>
</el-card>
</div>
</div>
</template>
<script>
import MsReportLogDetails from './components/LogDetails';
import ResultDetails from './components/ResultDetails';
import MsRequestResult from "./components/RequestResult";
import MsScenarioResult from "./components/ScenarioResult";
import MsMetricChart from "./components/MetricChart";
export default {
name: "ApiReportView",
components: {
MsReportLogDetails,
ResultDetails
},
name: "MsApiReportView",
components: {MsMetricChart, MsScenarioResult, MsRequestResult},
data() {
return {
content: {},
report: {},
result: {},
active: '0',
videoPath: '',
report: {}
}
},
methods: {
initBreadcrumb() {
if(this.reportId){
this.result = this.$get("/api/report/test/pro/info/" + this.reportId, res => {
let data = res.data;
if(data){
this.report = data;
this.report.content = JSON.parse(this.report.content);
}
getReport() {
if (this.reportId) {
let url = "/api/report/get/" + this.reportId;
this.result = this.$get(url, response => {
this.report = response.data || {};
this.content = JSON.parse(this.report.content);
});
}
}
},
mounted() {
this.initBreadcrumb();
watch: {
'$route': 'getReport',
},
created() {
this.getReport();
},
computed: {
reportId: function () {
return this.$route.params.reportId;
},
path() {
return "/api/test/edit?id=" + this.report.testId;
}
}
}
</script>
<style scoped>
.report-view-container {
float: none;
text-align: center;
padding: 15px;
width: 100%;
height: 100%;
box-sizing: border-box;
.report-container {
height: calc(100vh - 150px);
min-height: 600px;
overflow-y: auto;
}
.report-view-container .main-content {
margin: 0 auto;
width: 100%;
max-width: 1200px;
.report-header {
font-size: 15px;
}
.ms-report-view-btns {
margin-top: 15px;
.report-header a {
text-decoration: none;
}
.ms-report-time-desc {
text-align: left;
display: block;
color: #5C7878;
.scenario-header {
border: 1px solid #EBEEF5;
background-color: #F9FCFF;
border-left: 0;
border-right: 0;
padding: 5px 0;
}
</style>

View File

@ -0,0 +1,32 @@
<template>
<el-table :data="assertions" style="width: 100%">
<el-table-column prop="name" :label="$t('api_report.assertions_name')" width="300">
</el-table-column>
<el-table-column prop="message" :label="$t('api_report.assertions_message')">
</el-table-column>
<el-table-column prop="pass" :label="$t('api_report.assertions_is_success')" width="180">
<template v-slot:default="{row}">
<el-tag size="mini" type="success" v-if="row.pass">
{{$t('api_report.success')}}
</el-tag>
<el-tag size="mini" type="danger" v-else>
{{$t('api_report.fail')}}
</el-tag>
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
name: "MsAssertionResults",
props: {
assertions: Array
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,202 @@
<template>
<div class="metric-container">
<el-row type="flex" align="middle">
<div style="width: 50%">
<el-row type="flex" justify="center" align="middle">
<chart id="chart" ref="chart" :options="options" :autoresize="true"></chart>
<el-row type="flex" justify="center" align="middle">
<i class="circle success"/>
<div class="metric-box">
<div class="value">{{content.success}}</div>
<div class="name">{{$t('api_report.success')}}</div>
</div>
<div style="width: 40px"></div>
<i class="circle fail"/>
<div class="metric-box">
<div class="value">{{content.error}}</div>
<div class="name">{{$t('api_report.fail')}}</div>
</div>
</el-row>
</el-row>
</div>
<div class="split"></div>
<div style="width: 50%">
<el-row type="flex" justify="space-around" align="middle">
<div class="metric-icon-box">
<i class="el-icon-warning-outline fail"></i>
<div class="value">{{fail}}</div>
<div class="name">{{$t('api_report.fail')}}</div>
</div>
<div class="metric-icon-box">
<i class="el-icon-document-checked assertions"></i>
<div class="value">{{assertions}}</div>
<div class="name">{{$t('api_report.assertions_pass')}}</div>
</div>
<div class="metric-icon-box">
<i class="el-icon-document-copy total"></i>
<div class="value">{{this.content.total}}</div>
<div class="name">{{$t('api_report.request')}}</div>
</div>
</el-row>
</div>
</el-row>
</div>
</template>
<script>
export default {
name: "MsMetricChart",
props: {
content: Object
},
computed: {
options() {
return {
color: ['#67C23A', '#F56C6C'],
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
title: [{
text: '{value|' + this.content.total + '}\n{name|' + this.$t('api_report.request') + '}',
top: 'center',
left: 'center',
textStyle: {
rich: {
align: 'center',
value: {
fontSize: 32,
fontWeight: 'bold',
padding: [10, 0]
},
name: {
fontSize: 14,
fontWeight: 'normal',
color: '#7F7F7F',
}
}
}
}],
series: [
{
type: 'pie',
radius: ['80%', '90%'],
avoidLabelOverlap: false,
hoverAnimation: false,
itemStyle: {
normal: {
borderColor: "#FFF",
shadowColor: '#E1E1E1',
shadowBlur: 10
}
},
labelLine: {
show: false
},
data: [
{value: this.content.success, name: this.$t('api_report.success')},
{value: this.content.error, name: this.$t('api_report.fail')},
]
}
]
};
},
fail() {
return (this.content.error / this.content.total).toFixed(0) + "%";
},
assertions() {
return this.content.passAssertions + " / " + this.content.totalAssertions;
}
},
}
</script>
<style scoped>
.metric-container {
padding: 20px;
}
.metric-container #chart {
width: 140px;
height: 140px;
margin-right: 40px;
}
.metric-container .split {
margin: 20px;
height: 100px;
border-left: 1px solid #D8DBE1;
}
.metric-container .circle {
width: 12px;
height: 12px;
border-radius: 50%;
box-shadow: 0 0 20px 1px rgba(200, 216, 226, .42);
display: inline-block;
margin-right: 10px;
vertical-align: middle;
}
.metric-container .circle.success {
background-color: #67C23A;
}
.metric-container .circle.fail {
background-color: #F56C6C;
}
.metric-box {
display: inline-block;
text-align: center;
}
.metric-box .value {
font-size: 32px;
font-weight: 600;
letter-spacing: -.5px;
}
.metric-box .name {
font-size: 16px;
letter-spacing: -.2px;
color: #404040;
}
.metric-icon-box {
text-align: center;
margin: 0 10px;
}
.metric-icon-box .value {
font-size: 20px;
font-weight: 600;
letter-spacing: -.4px;
line-height: 28px;
vertical-align: middle;
}
.metric-icon-box .name {
font-size: 13px;
letter-spacing: 1px;
color: #BFBFBF;
line-height: 18px;
}
.metric-icon-box .fail {
color: #F56C6C;
font-size: 40px;
}
.metric-icon-box .assertions {
font-size: 40px;
}
.metric-icon-box .total {
font-size: 40px;
}
</style>

View File

@ -0,0 +1,101 @@
<template>
<div class="metric-container">
<el-row type="flex">
<div class="metric">
<div class="value">{{request.responseResult.responseTime}} ms</div>
<div class="name">{{$t('api_report.response_time')}}</div>
<br>
<div class="value">{{request.responseResult.latency}} ms</div>
<div class="name">{{$t('api_report.latency')}}</div>
</div>
<div class="metric">
<div class="value">{{request.requestSize}} bytes</div>
<div class="name">{{$t('api_report.request_size')}}</div>
<br>
<div class="value">{{request.responseResult.responseSize}} bytes</div>
<div class="name">{{$t('api_report.response_size')}}</div>
</div>
<div class="metric horizontal">
<el-row type="flex">
<div class="code">
<div class="value" :class="{'error': error}">{{request.responseResult.responseCode}}</div>
<div class="name">{{$t('api_report.response_code')}}</div>
</div>
<div class="split"></div>
<div class="message">
<div class="value">{{request.responseResult.responseMessage}}</div>
<div class="name">{{$t('api_report.response_message')}}</div>
</div>
</el-row>
</div>
</el-row>
</div>
</template>
<script>
export default {
name: "MsRequestMetric",
props: {
request: Object
},
computed: {
error() {
return this.request.responseResult.responseCode >= 400;
}
}
}
</script>
<style scoped>
.metric-container {
padding: 20px;
}
.metric {
padding: 20px;
border: 1px solid #EBEEF5;
min-width: 120px;
height: 114px;
}
.metric + .metric {
margin-left: 20px;
}
.metric .value {
font-size: 16px;
font-weight: 500;
word-break: break-all;
}
.metric .name {
color: #404040;
opacity: 0.5;
padding: 5px 0;
}
.metric.horizontal {
width: 100%;
}
.metric .code {
width: 120px;
}
.metric .code .value {
color: #67C23A;
}
.metric .code .value.error {
color: #F56C6C;
}
.metric .split {
height: 114px;
border-left: 1px solid #EBEEF5;
margin-right: 20px;
}
</style>

View File

@ -0,0 +1,122 @@
<template>
<div class="request-result">
<div @click="active">
<el-row :gutter="10" type="flex" align="middle" class="info">
<el-col :span="4">
<div class="method">
{{request.method}}
</div>
</el-col>
<el-col :span="12">
<div class="name">{{request.name}}</div>
<div class="url">{{request.url}}</div>
</el-col>
<el-col :span="2">
<div class="time">
{{request.responseResult.responseTime}}
</div>
</el-col>
<el-col :span="2">
{{error}}
</el-col>
<el-col :span="2">
{{request.passAssertions}} / {{request.totalAssertions}}
</el-col>
<el-col :span="2">
<el-tag size="mini" type="success" v-if="request.success">
{{$t('api_report.success')}}
</el-tag>
<el-tag size="mini" type="danger" v-else>
{{$t('api_report.fail')}}
</el-tag>
</el-col>
</el-row>
</div>
<el-collapse-transition>
<div v-show="isActive">
<ms-request-metric :request="request"/>
<ms-request-text :request="request"/>
<br>
<ms-response-text :response="request.responseResult"/>
</div>
</el-collapse-transition>
</div>
</template>
<script>
import MsRequestMetric from "./RequestMetric";
import MsAssertionResults from "./AssertionResults";
import MsRequestText from "./RequestText";
import MsResponseText from "./ResponseText";
export default {
name: "MsRequestResult",
components: {MsResponseText, MsRequestText, MsAssertionResults, MsRequestMetric},
props: {
request: Object
},
data() {
return {
isActive: false,
activeName: "request",
activeName2: "body",
activeName3: "body",
}
},
methods: {
active() {
this.isActive = !this.isActive;
}
},
computed: {
error() {
return this.request.totalAssertions - this.request.passAssertions;
}
}
}
</script>
<style scoped>
.request-result {
width: 100%;
min-height: 40px;
padding: 2px 0;
cursor: pointer;
}
.request-result .info {
background-color: #F9F9F9;
margin-left: 20px;
}
.request-result .method {
/*border-left: 5px solid #1E90FF;*/
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;
overflow: auto;
white-space: normal;
word-wrap: break-word;
}
.request-result .tab .el-tabs__header {
margin: 0;
}
.request-result .text {
height: 300px;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,69 @@
<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: false,
activeName: "body",
}
},
methods: {
active() {
this.isActive = !this.isActive;
}
},
}
</script>
<style scoped>
.text-container .icon {
padding: 5px;
}
.text-container .icon.is-active {
transform: rotate(90deg);
}
.text-container .collapse:hover {
opacity: 0.8;
}
.text-container .pane {
background-color: #F9F9F9;
padding: 10px;
height: 250px;
overflow-y: auto;
}
pre {
margin: 0;
}
</style>

View File

@ -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.response')}}
</div>
<el-collapse-transition>
<el-tabs v-model="activeName" v-show="isActive">
<el-tab-pane label="Body" name="body" class="pane">
<div>{{response.body}}</div>
</el-tab-pane>
<el-tab-pane label="Headers" name="headers" class="pane">
<pre>{{response.headers}}</pre>
</el-tab-pane>
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane">
<ms-assertion-results :assertions="response.assertions"/>
</el-tab-pane>
</el-tabs>
</el-collapse-transition>
</div>
</template>
<script>
import MsAssertionResults from "./AssertionResults";
export default {
name: "MsResponseText",
components: {MsAssertionResults},
props: {
response: Object
},
data() {
return {
isActive: false,
activeName: "body",
}
},
methods: {
active() {
this.isActive = !this.isActive;
}
},
}
</script>
<style scoped>
.text-container .icon {
padding: 5px;
}
.text-container .icon.is-active {
transform: rotate(90deg);
}
.text-container .collapse:hover {
opacity: 0.8;
}
.text-container .pane {
background-color: #F5F5F5;
padding: 0 10px;
height: 250px;
overflow-y: auto;
}
pre {
margin: 0;
}
</style>

View File

@ -0,0 +1,94 @@
<template>
<div class="scenario-result">
<div @click="active">
<el-row :gutter="10" type="flex" align="middle" class="info">
<el-col :span="16">
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
{{scenario.name}}
</el-col>
<el-col :span="2">
{{scenario.responseTime}}
</el-col>
<el-col :span="2">
{{scenario.error}}
</el-col>
<el-col :span="2">
{{assertion}}
</el-col>
<el-col :span="2">
<el-tag size="mini" type="success" v-if="success">
{{$t('api_report.success')}}
</el-tag>
<el-tag size="mini" type="danger" v-else>
{{$t('api_report.fail')}}
</el-tag>
</el-col>
</el-row>
</div>
<el-collapse-transition>
<div v-show="isActive">
<ms-request-result v-for="(request, index) in scenario.requestResults" :key="index" :request="request"/>
</div>
</el-collapse-transition>
</div>
</template>
<script>
import MsRequestResult from "./RequestResult";
export default {
name: "MsScenarioResult",
components: {MsRequestResult},
props: {
scenario: Object
},
data() {
return {
isActive: false
}
},
methods: {
active() {
this.isActive = !this.isActive;
}
},
computed: {
assertion() {
return this.scenario.passAssertions - this.scenario.totalAssertions;
},
success() {
return this.scenario.error === 0;
}
}
}
</script>
<style scoped>
.scenario-result {
width: 100%;
padding: 2px 0;
}
.scenario-result + .scenario-result {
border-top: 1px solid #EBEEF5;
}
.scenario-result .info {
height: 40px;
cursor: pointer;
}
.scenario-result .icon {
padding: 5px;
}
.scenario-result .icon.is-active {
transform: rotate(90deg);
}
</style>

View File

@ -38,6 +38,38 @@
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
prop="status"
:label="$t('commons.status')">
<template v-slot:default="{row}">
<el-tag size="mini" type="info" v-if="row.status === 'Saved'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="primary" v-else-if="row.status === 'Starting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<span v-else>
{{ row.status }}
</span>
</template>
</el-table-column>
<el-table-column
width="150"
:label="$t('commons.operating')">

View File

@ -234,6 +234,30 @@ export default {
extract: "提取",
}
},
api_report: {
request: "请求",
request_body: "请求内容",
request_headers: "请求头",
request_cookie: "Cookie",
response: "响应",
delete_confirm: '确认删除报告: ',
scenario_name: "场景名称",
response_time: "响应时间(ms)",
latency: "网络延迟",
request_size: "请求大小",
response_size: "响应大小",
response_code: "状态码",
response_message: "响应报文",
error: "错误",
assertions: "断言",
assertions_pass: "成功断言",
assertions_name: "断言名称",
assertions_message: "断言信息",
assertions_is_success: "是否成功",
result: "结果",
success: "成功",
fail: "失败",
},
test_track: {
test_track: "测试跟踪",
confirm: "确 定",