API报告页面
This commit is contained in:
parent
ae288e8ace
commit
2a7be35de1
|
@ -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")
|
||||
|
|
|
@ -9,5 +9,7 @@ import lombok.Setter;
|
|||
@Getter
|
||||
public class APIReportResult extends ApiTestReport {
|
||||
|
||||
private String testName;
|
||||
|
||||
private String projectName;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -9,6 +9,8 @@ public class RequestResult {
|
|||
|
||||
private String url;
|
||||
|
||||
private String method;
|
||||
|
||||
private long requestSize;
|
||||
|
||||
private boolean success;
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -13,4 +13,6 @@ public interface ExtApiTestReportMapper {
|
|||
|
||||
List<APIReportResult> listByTestId(@Param("testId") String testId);
|
||||
|
||||
APIReportResult get(@Param("id") String id);
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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')">
|
||||
|
|
|
@ -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: "确 定",
|
||||
|
|
Loading…
Reference in New Issue