fix(测试跟踪): 修复测试计划报告导出时性能测试报告详情不显示的问题

--bug=1024105 --user=宋天阳 【测试跟踪】测试计划导出的报告-性能测试用例没有数据
https://www.tapd.cn/55049933/s/1350582
This commit is contained in:
song-tianyang 2023-03-15 15:49:38 +08:00 committed by 建国
parent a295a39ecb
commit 2c1fe4c70b
12 changed files with 1559 additions and 1090 deletions

View File

@ -531,7 +531,7 @@ public class TestPlanLoadCaseService {
} }
public void buildLoadResponse(List<TestPlanLoadCaseDTO> cases) { public void buildLoadResponse(List<TestPlanLoadCaseDTO> cases) {
if (!org.apache.commons.collections.CollectionUtils.isEmpty(cases)) { if (!CollectionUtils.isEmpty(cases)) {
cases.forEach(item -> { cases.forEach(item -> {
LoadCaseReportRequest request = new LoadCaseReportRequest(); LoadCaseReportRequest request = new LoadCaseReportRequest();
String reportId = item.getLoadReportId(); String reportId = item.getLoadReportId();

View File

@ -0,0 +1,16 @@
package io.metersphere.dto;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class LoadTestExportJmx {
private String name;
private String jmx;
public LoadTestExportJmx(String name, String jmx) {
this.name = name;
this.jmx = jmx;
}
}

View File

@ -2,14 +2,15 @@ package io.metersphere.dto;
import io.metersphere.base.domain.LoadTestReportWithBLOBs; import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import io.metersphere.base.domain.TestPlanLoadCaseWithBLOBs; import io.metersphere.base.domain.TestPlanLoadCaseWithBLOBs;
import io.metersphere.plan.dto.ChartsData; import io.metersphere.plan.dto.ChartsData;
import io.metersphere.plan.dto.Errors; import io.metersphere.plan.dto.Errors;
import io.metersphere.plan.dto.ErrorsTop5; import io.metersphere.plan.dto.ErrorsTop5;
import io.metersphere.plan.dto.Statistics; import io.metersphere.plan.dto.Statistics;
import io.opentelemetry.sdk.metrics.data.MetricData;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import javax.management.monitor.Monitor;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -34,8 +35,7 @@ public class TestPlanLoadCaseDTO extends TestPlanLoadCaseWithBLOBs {
private long startTime; private long startTime;
private long endTime; private long endTime;
private String fixLoadConfiguration; private String fixLoadConfiguration;
// private LoadTestExportJmx jmxContent; private List<LoadTestExportJmx> fixJmxContent;
// private List<LoadTestExportJmx> fixJmxContent;
private TestOverview testOverview; private TestOverview testOverview;
private List<ChartsData> loadChartData; private List<ChartsData> loadChartData;
private List<ChartsData> responseTimeChartData; private List<ChartsData> responseTimeChartData;
@ -46,8 +46,8 @@ public class TestPlanLoadCaseDTO extends TestPlanLoadCaseWithBLOBs {
private List<Errors> reportErrors; private List<Errors> reportErrors;
private List<ErrorsTop5> reportErrorsTop5; private List<ErrorsTop5> reportErrorsTop5;
private List<LogDetailDTO> reportLogResource; private List<LogDetailDTO> reportLogResource;
// private List<Monitor> reportResource; private List<Monitor> reportResource;
// private List<MetricData> metricData; private List<MetricData> metricData;
private List<TestResourcePoolDTO> resourcePools; private List<TestResourcePoolDTO> resourcePools;
} }
} }

View File

@ -1,41 +1,101 @@
<template> <template>
<div> <div>
<el-row > <el-row>
<el-col :span="12"> <el-col :span="12">
<ms-doughnut-pie-chart :name="$t('test_track.plan.test_plan_test_case_count')" :data="caseCharData" ref="functionChar"/> <ms-doughnut-pie-chart
</el-col> :name="$t('test_track.plan.test_plan_test_case_count')"
<el-col :span="12"> :data="caseCharData"
<ms-doughnut-pie-chart :name="$t('test_track.plan_view.issues_count')" :data="issueCharData"/> ref="functionChar"
</el-col> />
</el-row> </el-col>
</div> <el-col :span="12">
<ms-doughnut-pie-chart
:name="$t('test_track.plan_view.issues_count')"
:data="issueCharData"
/>
</el-col>
</el-row>
</div>
</template> </template>
<script> <script>
import MsDoughnutPieChart from "metersphere-frontend/src/components/MsDoughnutPieChart"; import MsDoughnutPieChart from "metersphere-frontend/src/components/MsDoughnutPieChart";
export default { export default {
name: "FunctionalResult", name: "FunctionalResult",
components: {MsDoughnutPieChart}, components: { MsDoughnutPieChart },
data() { data() {
return { return {
caseDataMap: new Map([ caseDataMap: new Map([
["Pass", {name: this.$t('test_track.plan_view.pass'), itemStyle: {color: '#67C23A'}}], [
["Failure", {name: this.$t('test_track.plan_view.failure'), itemStyle: {color: '#F56C6C'}}], "Pass",
["Blocking", {name: this.$t('test_track.plan_view.blocking'), itemStyle: {color: '#E6A23C'}}], {
["Skip", {name: this.$t('test_track.plan_view.skip'), itemStyle: {color: '#909399'}}], name: this.$t("test_track.plan_view.pass"),
["Underway", {name: this.$t('test_track.plan.plan_status_running'), itemStyle: {color: 'lightskyblue'}}], itemStyle: { color: "#67C23A" },
["Prepare", {name: this.$t('test_track.plan.plan_status_prepare'), itemStyle: {color: '#DEDE10'}}] },
],
[
"Failure",
{
name: this.$t("test_track.plan_view.failure"),
itemStyle: { color: "#F56C6C" },
},
],
[
"Blocking",
{
name: this.$t("test_track.plan_view.blocking"),
itemStyle: { color: "#E6A23C" },
},
],
[
"Skip",
{
name: this.$t("test_track.plan_view.skip"),
itemStyle: { color: "#909399" },
},
],
[
"Underway",
{
name: this.$t("test_track.plan.plan_status_running"),
itemStyle: { color: "lightskyblue" },
},
],
[
"Prepare",
{
name: this.$t("test_track.plan.plan_status_prepare"),
itemStyle: { color: "#DEDE10" },
},
],
]), ]),
issueDataMap: new Map([ issueDataMap: new Map([
["resolved", {name: this.$t('test_track.issue.status_resolved'), itemStyle: {color: '#67C23A'}}], [
["new", {name: this.$t('test_track.issue.status_new'), itemStyle: {color: '#F56C6C'}}], "resolved",
["closed", {name: this.$t('test_track.issue.status_closed'), itemStyle: {color: '#909399'}}], {
name: this.$t("test_track.issue.status_resolved"),
itemStyle: { color: "#67C23A" },
},
],
[
"new",
{
name: this.$t("test_track.issue.status_new"),
itemStyle: { color: "#F56C6C" },
},
],
[
"closed",
{
name: this.$t("test_track.issue.status_closed"),
itemStyle: { color: "#909399" },
},
],
]), ]),
caseCharData: [], caseCharData: [],
issueCharData: [], issueCharData: [],
isShow: true isShow: true,
} };
}, },
props: { props: {
functionResult: { functionResult: {
@ -43,15 +103,15 @@ export default {
default() { default() {
return { return {
caseData: [], caseData: [],
issueData: [] issueData: [],
} };
} },
} },
}, },
watch: { watch: {
functionResult() { functionResult() {
this.getCaseCharData(); this.getCaseCharData();
} },
}, },
created() { created() {
this.getCaseCharData(); this.getCaseCharData();
@ -59,7 +119,7 @@ export default {
methods: { methods: {
getCaseCharData() { getCaseCharData() {
let caseCharData = []; let caseCharData = [];
this.functionResult.caseData.forEach(item => { this.functionResult.caseData.forEach((item) => {
let data = this.caseDataMap.get(item.status); let data = this.caseDataMap.get(item.status);
data.value = item.count; data.value = item.count;
caseCharData.push(data); caseCharData.push(data);
@ -67,46 +127,51 @@ export default {
this.caseCharData = caseCharData; this.caseCharData = caseCharData;
let issueCharData = []; let issueCharData = [];
let colors = ['#67C23A', '#E6A23C','#DEDE10', let colors = ["#67C23A", "#E6A23C", "#DEDE10", "#F56C6C", "#909399"];
'#F56C6C','#909399'];
let usedSet = new Set(); let usedSet = new Set();
if (this.functionResult.issueData) {
this.functionResult.issueData.forEach(item => { this.functionResult.issueData.forEach((item) => {
let status = item.status; let status = item.status;
let data = this.issueDataMap.get(status); let data = this.issueDataMap.get(status);
if (!data) { if (!data) {
data = {name: status, itemStyle: {color: null}}; data = { name: status, itemStyle: { color: null } };
if (status === 'new' || status === '新' || status === '待办' || status === 'active' || status === 'created') { if (
data.itemStyle.color = '#F56C6C'; status === "new" ||
usedSet.add(data.itemStyle.color); status === "新" ||
} status === "待办" ||
if (status === '已拒绝' || status === 'reject') { status === "active" ||
data.itemStyle.color = '#909399'; status === "created"
usedSet.add(data.itemStyle.color); ) {
} data.itemStyle.color = "#F56C6C";
if (status === '已关闭' || status === 'close') { usedSet.add(data.itemStyle.color);
data.itemStyle.color = '#67C23A'; }
usedSet.add(data.itemStyle.color); if (status === "已拒绝" || status === "reject") {
} data.itemStyle.color = "#909399";
if (!data.itemStyle.color) { usedSet.add(data.itemStyle.color);
for (let i = 0; i < colors.length; i++) { }
let color = colors[i]; if (status === "已关闭" || status === "close") {
if (!usedSet.has(color)) { data.itemStyle.color = "#67C23A";
data.itemStyle.color = color; usedSet.add(data.itemStyle.color);
usedSet.add(data.itemStyle.color); }
break; if (!data.itemStyle.color) {
for (let i = 0; i < colors.length; i++) {
let color = colors[i];
if (!usedSet.has(color)) {
data.itemStyle.color = color;
usedSet.add(data.itemStyle.color);
break;
}
} }
} }
} }
} data.value = item.count;
data.value = item.count; issueCharData.push(data);
issueCharData.push(data); });
}); }
this.issueCharData = issueCharData; this.issueCharData = issueCharData;
}, },
} },
} };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -6,30 +6,30 @@
border border
stripe stripe
style="width: 100%" style="width: 100%"
:default-sort="{prop: 'elementLabel'}" :default-sort="{ prop: 'elementLabel' }"
> >
<el-table-column <el-table-column prop="errorType" label="Type of error" sortable>
prop="errorType"
label="Type of error"
sortable>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
width="200" width="200"
prop="errorNumber" prop="errorNumber"
label="Number of errors" label="Number of errors"
sortable> sortable
>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
width="200" width="200"
prop="percentOfErrors" prop="percentOfErrors"
label="% in errors" label="% in errors"
sortable> sortable
>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
width="200" width="200"
prop="percentOfAllSamples" prop="percentOfAllSamples"
label="% in all samples" label="% in all samples"
sortable> sortable
>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -41,75 +41,49 @@
style="width: 100%" style="width: 100%"
show-summary show-summary
> >
<el-table-column prop="sample" label="Sample"/> <el-table-column prop="sample" label="Sample" />
<el-table-column prop="samples" label="#Samples"/> <el-table-column prop="samples" label="#Samples" />
<el-table-column prop="errorsAllSize" label="All Errors"/> <el-table-column prop="errorsAllSize" label="All Errors" />
</el-table> </el-table>
<span class="table-title">#1 Error</span> <span class="table-title">#1 Error</span>
<el-table <el-table :data="errorTop1" border stripe style="width: 100%">
:data="errorTop1" <el-table-column prop="sample" label="Sample" />
border <el-table-column prop="error1" label="#1 Error" />
stripe <el-table-column prop="error1Size" label="#1 Errors Count" width="200" />
style="width: 100%"
>
<el-table-column prop="sample" label="Sample"/>
<el-table-column prop="error1" label="#1 Error"/>
<el-table-column prop="error1Size" label="#1 Errors Count" width="200"/>
</el-table> </el-table>
<span class="table-title">#2 Error</span> <span class="table-title">#2 Error</span>
<el-table <el-table :data="errorTop2" border stripe style="width: 100%">
:data="errorTop2" <el-table-column prop="sample" label="Sample" />
border <el-table-column prop="error2" label="#2 Error" />
stripe <el-table-column prop="error2Size" label="#2 Errors Count" width="200" />
style="width: 100%"
>
<el-table-column prop="sample" label="Sample"/>
<el-table-column prop="error2" label="#2 Error"/>
<el-table-column prop="error2Size" label="#2 Errors Count" width="200"/>
</el-table> </el-table>
<span class="table-title">#3 Error</span> <span class="table-title">#3 Error</span>
<el-table <el-table :data="errorTop3" border stripe style="width: 100%">
:data="errorTop3" <el-table-column prop="sample" label="Sample" />
border <el-table-column prop="error3" label="#3 Error" />
stripe <el-table-column prop="error3Size" label="#3 Errors Count" width="200" />
style="width: 100%"
>
<el-table-column prop="sample" label="Sample"/>
<el-table-column prop="error3" label="#3 Error"/>
<el-table-column prop="error3Size" label="#3 Errors Count" width="200"/>
</el-table> </el-table>
<span class="table-title">#4 Error</span> <span class="table-title">#4 Error</span>
<el-table <el-table :data="errorTop4" border stripe style="width: 100%">
:data="errorTop4" <el-table-column prop="sample" label="Sample" />
border <el-table-column prop="error4" label="#4 Error" />
stripe <el-table-column prop="error4Size" label="#4 Errors Count" width="200" />
style="width: 100%"
>
<el-table-column prop="sample" label="Sample"/>
<el-table-column prop="error4" label="#4 Error"/>
<el-table-column prop="error4Size" label="#4 Errors Count" width="200"/>
</el-table> </el-table>
<span class="table-title">#5 Error</span> <span class="table-title">#5 Error</span>
<el-table <el-table :data="errorTop5" border stripe style="width: 100%">
:data="errorTop5" <el-table-column prop="sample" label="Sample" />
border <el-table-column prop="error5" label="#5 Error" />
stripe <el-table-column prop="error5Size" label="#5 Errors Count" width="200" />
style="width: 100%"
>
<el-table-column prop="sample" label="Sample"/>
<el-table-column prop="error5" label="#5 Error"/>
<el-table-column prop="error5Size" label="#5 Errors Count" width="200"/>
</el-table> </el-table>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "ErrorLog", name: "ErrorLog",
data() { data() {
@ -121,10 +95,13 @@ export default {
errorTop3: [], errorTop3: [],
errorTop4: [], errorTop4: [],
errorTop5: [], errorTop5: [],
id: '' id: "",
}; };
}, },
props: ['report', 'isShare', 'shareId', 'planReportTemplate'], props: ["report", "isShare", "shareId", "planReportTemplate"],
created() {
this.initTableData();
},
methods: { methods: {
initTableData() { initTableData() {
if (this.planReportTemplate) { if (this.planReportTemplate) {
@ -137,37 +114,61 @@ export default {
return; return;
} }
this.errorTop1 = data this.errorTop1 = data
.map(e => { .map((e) => {
return {sample: e.sample, error1: e.error1, error1Size: e.error1Size}; return {
sample: e.sample,
error1: e.error1,
error1Size: e.error1Size,
};
}) })
.filter(e => e.error1Size > 0); .filter((e) => e.error1Size > 0);
this.errorTop2 = data this.errorTop2 = data
.map(e => { .map((e) => {
return {sample: e.sample, error2: e.error2, error2Size: e.error2Size}; return {
sample: e.sample,
error2: e.error2,
error2Size: e.error2Size,
};
}) })
.filter(e => e.error2Size > 0); .filter((e) => e.error2Size > 0);
this.errorTop3 = data this.errorTop3 = data
.map(e => { .map((e) => {
return {sample: e.sample, error3: e.error3, error3Size: e.error3Size}; return {
sample: e.sample,
error3: e.error3,
error3Size: e.error3Size,
};
}) })
.filter(e => e.error3Size > 0); .filter((e) => e.error3Size > 0);
this.errorTop4 = data this.errorTop4 = data
.map(e => { .map((e) => {
return {sample: e.sample, error4: e.error4, error4Size: e.error4Size}; return {
sample: e.sample,
error4: e.error4,
error4Size: e.error4Size,
};
}) })
.filter(e => e.error4Size > 0); .filter((e) => e.error4Size > 0);
this.errorTop5 = data this.errorTop5 = data
.map(e => { .map((e) => {
return {sample: e.sample, error5: e.error5, error5Size: e.error5Size}; return {
sample: e.sample,
error5: e.error5,
error5Size: e.error5Size,
};
}) })
.filter(e => e.error5Size > 0); .filter((e) => e.error5Size > 0);
this.errorSummary = data.map(e => { this.errorSummary = data.map((e) => {
return {sample: e.sample, samples: e.samples, errorsAllSize: e.errorsAllSize}; return {
sample: e.sample,
samples: e.samples,
errorsAllSize: e.errorsAllSize,
};
}); });
}, },
initData() { initData() {
@ -178,7 +179,7 @@ export default {
this.errorTop4 = []; this.errorTop4 = [];
this.errorTop5 = []; this.errorTop5 = [];
this.errorSummary = []; this.errorSummary = [];
} },
}, },
watch: { watch: {
report: { report: {
@ -200,7 +201,7 @@ export default {
this.errorSummary = []; this.errorSummary = [];
} }
}, },
deep: true deep: true,
}, },
planReportTemplate: { planReportTemplate: {
handler() { handler() {
@ -208,8 +209,8 @@ export default {
this.initTableData(); this.initTableData();
} }
}, },
deep: true deep: true,
} },
}, },
}; };
</script> </script>

View File

@ -2,21 +2,30 @@
<div> <div>
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="4"> <el-col :span="4">
<el-select v-model="currentInstance" placeholder="" size="small" style="width: 100%" <el-select
@change="changeInstance(currentInstance)"> v-model="currentInstance"
placeholder=""
size="small"
style="width: 100%"
@change="changeInstance(currentInstance)"
>
<el-option <el-option
v-for="item in resource" v-for="item in resource"
:key="item.resourceId" :key="item.resourceId"
:label="item.resourceName" :label="item.resourceName"
:value="item.resourceId"> :value="item.resourceId"
>
</el-option> </el-option>
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="20"> <el-col :span="20">
<div class="logging-content" v-loading="loading"> <div class="logging-content" v-loading="loading">
<ul class="infinite-list"> <ul class="infinite-list">
<li class="infinite-list-item" v-for="(log, index) in logContent" <li
:key="currentInstance+index"> class="infinite-list-item"
v-for="(log, index) in logContent"
:key="currentInstance + index"
>
{{ log.content }} {{ log.content }}
</li> </li>
</ul> </ul>
@ -27,7 +36,6 @@
</template> </template>
<script> <script>
export default { export default {
name: "LogDetails", name: "LogDetails",
data() { data() {
@ -35,14 +43,17 @@ export default {
resource: [], resource: [],
logContent: [], logContent: [],
result: {}, result: {},
id: '', id: "",
page: 1, page: 1,
pageCount: 5, pageCount: 5,
loading: false, loading: false,
currentInstance: '' currentInstance: "",
}; };
}, },
props: ['report', 'export', 'isShare', 'shareId', 'planReportTemplate'], props: ["report", "export", "isShare", "shareId", "planReportTemplate"],
created() {
this.getResource();
},
methods: { methods: {
getResource() { getResource() {
if (this.planReportTemplate) { if (this.planReportTemplate) {
@ -66,9 +77,9 @@ export default {
} }
this.loading = true; this.loading = true;
if (this.planReportTemplate) { if (this.planReportTemplate) {
let {reportLogResource} = this.planReportTemplate; let { reportLogResource } = this.planReportTemplate;
if (reportLogResource && reportLogResource.length > 0) { if (reportLogResource && reportLogResource.length > 0) {
let {reportLogs} = reportLogResource[0]; let { reportLogs } = reportLogResource[0];
if (reportLogs) { if (reportLogs) {
this.handleGetPlanTemplateLog(reportLogs); this.handleGetPlanTemplateLog(reportLogs);
} }
@ -76,7 +87,7 @@ export default {
} }
}, },
handleGetPlanTemplateLog(data) { handleGetPlanTemplateLog(data) {
data.forEach(log => { data.forEach((log) => {
if (this.logContent) { if (this.logContent) {
this.logContent.push(log); this.logContent.push(log);
} }
@ -92,9 +103,9 @@ export default {
}, },
}, },
watch: { watch: {
'$route'(to) { $route(to) {
if (to.name === "perReportView") { if (to.name === "perReportView") {
this.id = to.path.split('/')[4]; this.id = to.path.split("/")[4];
this.getResource(); this.getResource();
} }
}, },
@ -109,7 +120,7 @@ export default {
this.getResource(); this.getResource();
} }
}, },
deep: true deep: true,
}, },
planReportTemplate: { planReportTemplate: {
handler() { handler() {
@ -117,8 +128,8 @@ export default {
this.getResource(); this.getResource();
} }
}, },
deep: true deep: true,
} },
}, },
}; };
</script> </script>
@ -134,11 +145,10 @@ export default {
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
overflow: auto overflow: auto;
} }
.infinite-list-item { .infinite-list-item {
overflow: hidden; overflow: hidden;
} }
</style> </style>

View File

@ -3,24 +3,30 @@
<el-row> <el-row>
<el-col :span="4"> <el-col :span="4">
<div> <div>
<el-select v-model="currentInstance" placeholder="" size="small" style="width: 100%" <el-select
@change="getResource(currentInstance)"> v-model="currentInstance"
placeholder=""
size="small"
style="width: 100%"
@change="getResource(currentInstance)"
>
<el-option <el-option
v-for="item in instances" v-for="item in instances"
:key="item.ip+item.port" :key="item.ip + item.port"
:value="item.ip+':'+item.port"> :value="item.ip + ':' + item.port"
>
{{ item.ip }} {{ item.name }} {{ item.ip }} {{ item.name }}
</el-option> </el-option>
</el-select> </el-select>
</div> </div>
<div style="padding-top: 10px"> <div style="padding-top: 10px">
<el-checkbox-group v-model="checkList" <el-checkbox-group
@change="handleCheckListChange(currentInstance)"> v-model="checkList"
<div v-for="op in checkOptions" @change="handleCheckListChange(currentInstance)"
:key="op.key" >
:content="op.label"> <div v-for="op in checkOptions" :key="op.key" :content="op.label">
<el-checkbox :label="op.label"/> <el-checkbox :label="op.label" />
</div> </div>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
@ -28,56 +34,31 @@
<el-col :span="20"> <el-col :span="20">
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<ms-chart v-if="showChart" ref="chart2" class="chart-config" @datazoom="changeDataZoom" <ms-chart
:options="totalOption" v-if="showChart"
:autoresize="true"></ms-chart> ref="chart2"
class="chart-config"
@datazoom="changeDataZoom"
:options="totalOption"
:autoresize="true"
></ms-chart>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
<el-col :offset="2" :span="20"> <el-col :offset="2" :span="20">
<el-table <el-table :data="tableData" stripe border style="width: 100%">
:data="tableData"
stripe
border
style="width: 100%">
<el-table-column label="Label" align="center"> <el-table-column label="Label" align="center">
<el-table-column <el-table-column prop="label" label="Label" sortable>
prop="label"
label="Label"
sortable>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="Aggregate" align="center"> <el-table-column label="Aggregate" align="center">
<el-table-column <el-table-column prop="avg" label="Avg." width="100" sortable />
prop="avg" <el-table-column prop="min" label="Min." width="100" sortable />
label="Avg." <el-table-column prop="max" label="Max." width="100" sortable />
width="100"
sortable
/>
<el-table-column
prop="min"
label="Min."
width="100"
sortable
/>
<el-table-column
prop="max"
label="Max."
width="100"
sortable
/>
</el-table-column> </el-table-column>
<el-table-column label="Range" align="center"> <el-table-column label="Range" align="center">
<el-table-column <el-table-column prop="startTime" label="Start" width="160" />
prop="startTime" <el-table-column prop="endTime" label="End" width="160" />
label="Start"
width="160"
/>
<el-table-column
prop="endTime"
label="End"
width="160"
/>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-col> </el-col>
@ -88,31 +69,42 @@
</template> </template>
<script> <script>
import MsChart from "metersphere-frontend/src/components/chart/MsChart"; import MsChart from "metersphere-frontend/src/components/chart/MsChart";
const color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3']; const color = [
const checkList = ['CPU', 'Memory', 'Disk', 'Network In', 'Network Out']; "#60acfc",
"#32d3eb",
"#5bc49f",
"#feb64d",
"#ff7c7c",
"#9287e7",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
const checkList = ["CPU", "Memory", "Disk", "Network In", "Network Out"];
const checkOptions = [ const checkOptions = [
{key: 'cpu', label: 'CPU'}, { key: "cpu", label: "CPU" },
{key: 'memory', label: 'Memory'}, { key: "memory", label: "Memory" },
{key: 'disk', label: 'Disk'}, { key: "disk", label: "Disk" },
{key: 'netIn', label: 'Network In'}, { key: "netIn", label: "Network In" },
{key: 'netOut', label: 'Network Out'} { key: "netOut", label: "Network Out" },
]; ];
export default { export default {
name: "MonitorCard", name: "MonitorCard",
props: ['report', 'export', 'isShare', 'shareId', 'planReportTemplate'], props: ["report", "export", "isShare", "shareId", "planReportTemplate"],
components: {MsChart}, components: { MsChart },
data() { data() {
return { return {
activeNames: '0', activeNames: "0",
result: {}, result: {},
id: '', id: "",
init: false, init: false,
loading: false, loading: false,
currentInstance: '', currentInstance: "",
instances: [], instances: [],
data: [], data: [],
tableData: [], tableData: [],
@ -126,37 +118,40 @@ export default {
}, },
title: {}, title: {},
tooltip: { tooltip: {
trigger: 'axis', trigger: "axis",
axisPointer: { axisPointer: {
type: 'cross' type: "cross",
}, },
}, },
legend: { legend: {
y: 'top' y: "top",
}, },
xAxis: {type: 'category'}, xAxis: { type: "category" },
yAxis: [{ yAxis: [
name: 'Usage(%)', {
type: 'value', name: "Usage(%)",
min: 0, type: "value",
max: 100, min: 0,
}, { max: 100,
type: 'value', },
name: 'kb/s', {
min: 0, type: "value",
}], name: "kb/s",
min: 0,
},
],
dataZoom: [ dataZoom: [
{ {
type: 'inside', type: "inside",
start: 0, start: 0,
end: 100 end: 100,
}, },
{ {
start: 0, start: 0,
end: 20 end: 20,
} },
], ],
series: [] series: [],
}, },
totalOption: {}, totalOption: {},
seriesData: [], seriesData: [],
@ -165,25 +160,35 @@ export default {
created() { created() {
this.data = []; this.data = [];
this.instances = []; this.instances = [];
this.getResource();
}, },
methods: { methods: {
getResource(currentInstance) { getResource(currentInstance) {
// this.init = true; // this.init = true;
if (this.planReportTemplate) { if (this.planReportTemplate) {
this.instances = this.planReportTemplate.reportResource; this.instances = this.planReportTemplate.reportResource;
this.currentInstance = currentInstance || this.instances[0]?.ip + ":" + this.instances[0]?.port; if (this.instances) {
this.data = this.planReportTemplate.metricData; this.currentInstance =
this.totalOption = this.getOption(this.currentInstance); currentInstance ||
this.instances[0]?.ip + ":" + this.instances[0]?.port;
}
if (this.planReportTemplate.metricData) {
this.data = this.planReportTemplate.metricData;
this.totalOption = this.getOption(this.currentInstance);
}
} }
}, },
handleChecked(id) { handleChecked(id) {
let curr = this.instances.filter(instance => id === instance.ip + ":" + instance.port)[0]; let curr = this.instances.filter(
(instance) => id === instance.ip + ":" + instance.port
)[0];
if (curr && curr.monitorConfig) { if (curr && curr.monitorConfig) {
this.checkList = []; this.checkList = [];
this.checkOptions = curr.monitorConfig.filter(mc => mc.value && mc.name) this.checkOptions = curr.monitorConfig
.map(mc => { .filter((mc) => mc.value && mc.name)
.map((mc) => {
this.checkList.push(mc.name); this.checkList.push(mc.name);
return {key: mc.name, label: mc.name,}; return { key: mc.name, label: mc.name };
}); });
if (this.checkList.length === 0) { if (this.checkList.length === 0) {
this.checkList = checkList; this.checkList = checkList;
@ -196,7 +201,7 @@ export default {
this.totalOption = {}; this.totalOption = {};
this.$nextTick(() => { this.$nextTick(() => {
this.totalOption = this.getOption(id); this.totalOption = this.getOption(id);
this.changeDataZoom({start: 0, end: 100}); this.changeDataZoom({ start: 0, end: 100 });
}); });
}, },
handleCheckListChange(id) { handleCheckListChange(id) {
@ -205,7 +210,7 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.showChart = true; this.showChart = true;
this.totalOption = this.getOption(id); this.totalOption = this.getOption(id);
this.changeDataZoom({start: 0, end: 100}); this.changeDataZoom({ start: 0, end: 100 });
}); });
}, },
getOption(id) { getOption(id) {
@ -213,12 +218,12 @@ export default {
let series = []; let series = [];
for (const name of this.checkList) { for (const name of this.checkList) {
let check = this.checkOptions.filter(op => op.label === name)[0].key; let check = this.checkOptions.filter((op) => op.label === name)[0].key;
let yAxisIndex = 1; let yAxisIndex = 1;
if (check === 'cpu' || check === 'memory' || check === 'disk') { if (check === "cpu" || check === "memory" || check === "disk") {
yAxisIndex = 0; yAxisIndex = 0;
} }
this.data.forEach(d => { this.data.forEach((d) => {
if (d.instance === id && d.seriesName === check) { if (d.instance === id && d.seriesName === check) {
if (legend.indexOf(name) > -1) { if (legend.indexOf(name) > -1) {
return; return;
@ -226,7 +231,7 @@ export default {
this.baseOption.xAxis.data = d.timestamps; this.baseOption.xAxis.data = d.timestamps;
let yAxis = d.values.map(v => v.toFixed(2)); let yAxis = d.values.map((v) => v.toFixed(2));
let data = []; let data = [];
for (let i = 0; i < d.timestamps.length; i++) { for (let i = 0; i < d.timestamps.length; i++) {
data.push([d.timestamps[i], yAxis[i]]); data.push([d.timestamps[i], yAxis[i]]);
@ -236,10 +241,10 @@ export default {
series.push({ series.push({
name: name, name: name,
data: data, data: data,
type: 'line', type: "line",
yAxisIndex: yAxisIndex, yAxisIndex: yAxisIndex,
smooth: true, smooth: true,
sampling: 'lttb', sampling: "lttb",
showSymbol: false, showSymbol: false,
}); });
@ -261,9 +266,15 @@ export default {
let tableData = []; let tableData = [];
for (let i = 0; i < this.seriesData.length; i++) { for (let i = 0; i < this.seriesData.length; i++) {
let sub = this.seriesData[i].data, label = this.seriesData[i].name; let sub = this.seriesData[i].data,
label = this.seriesData[i].name;
let len = 0; let len = 0;
let min, avg, max, sum = 0, startTime, endTime; let min,
avg,
max,
sum = 0,
startTime,
endTime;
for (let j = 0; j < sub.length; j++) { for (let j = 0; j < sub.length; j++) {
let time = sub[j][0]; let time = sub[j][0];
let value = Number.parseFloat(sub[j][1]); let value = Number.parseFloat(sub[j][1]);
@ -296,7 +307,7 @@ export default {
} }
avg = (sum / len).toFixed(2); avg = (sum / len).toFixed(2);
tableData.push({label, min, max, avg, startTime, endTime}); tableData.push({ label, min, max, avg, startTime, endTime });
} }
this.tableData = tableData; this.tableData = tableData;
}, },
@ -318,7 +329,7 @@ export default {
this.instances = []; this.instances = [];
} }
}, },
deep: true deep: true,
}, },
planReportTemplate: { planReportTemplate: {
handler() { handler() {
@ -326,8 +337,8 @@ export default {
this.getResource(); this.getResource();
} }
}, },
deep: true deep: true,
} },
}, },
}; };
</script> </script>
@ -342,7 +353,7 @@ export default {
overflow: auto; overflow: auto;
} }
:deep(.el-checkbox__label ) { :deep(.el-checkbox__label) {
font-size: 10px !important; font-size: 10px !important;
} }
</style> </style>

View File

@ -8,35 +8,31 @@
style="width: 100%" style="width: 100%"
> >
<el-table-column label="Requests" min-width="150" align="center"> <el-table-column label="Requests" min-width="150" align="center">
<el-table-column <el-table-column prop="label" label="Label" sortable min-width="150">
prop="label" <template v-slot:header="{ column }">
label="Label"
sortable
min-width="150">
<template v-slot:header="{column}">
<span>Label</span> <span>Label</span>
<i class="el-icon-search" style="margin-left: 8px;cursor: pointer;font-weight: bold;" <i
@click="click(column)"></i> class="el-icon-search"
<el-input v-model="searchLabel" style="margin-left: 8px; cursor: pointer; font-weight: bold"
placeholder="请输入 Label 搜索" @click="click(column)"
size="mini" ></i>
class="search_input" <el-input
style="width: 100px; margin-left: 5px" v-model="searchLabel"
v-if="column.showSearch" placeholder="请输入 Label 搜索"
clearable size="mini"
@clear="filterLabel" class="search_input"
@keyup.enter.native="filterLabel"/> style="width: 100px; margin-left: 5px"
v-if="column.showSearch"
clearable
@clear="filterLabel"
@keyup.enter.native="filterLabel"
/>
</template> </template>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="Executions" align="center"> <el-table-column label="Executions" align="center">
<el-table-column <el-table-column prop="samples" label="Samples" sortable width="110" />
prop="samples"
label="Samples"
sortable
width="110"
/>
<el-table-column <el-table-column
prop="fail" prop="fail"
@ -46,57 +42,17 @@
min-width="60" min-width="60"
/> />
<el-table-column <el-table-column prop="error" label="Error%" sortable align="center" />
prop="error"
label="Error%"
sortable
align="center"
/>
</el-table-column> </el-table-column>
<el-table-column label="Response Times(ms)" align="center"> <el-table-column label="Response Times(ms)" align="center">
<el-table-column <el-table-column prop="average" label="Avg" sortable min-width="60" />
prop="average" <el-table-column prop="min" label="Min" sortable min-width="60" />
label="Avg" <el-table-column prop="max" label="Max" sortable min-width="60" />
sortable <el-table-column prop="median" label="Med" sortable min-width="60" />
min-width="60" <el-table-column prop="tp90" label="90%" sortable min-width="60" />
/> <el-table-column prop="tp95" label="95%" sortable min-width="60" />
<el-table-column <el-table-column prop="tp99" label="99%" sortable min-width="60" />
prop="min"
label="Min"
sortable
min-width="60"
/>
<el-table-column
prop="max"
label="Max"
sortable
min-width="60"
/>
<el-table-column
prop="median"
label="Med"
sortable
min-width="60"
/>
<el-table-column
prop="tp90"
label="90%"
sortable
min-width="60"
/>
<el-table-column
prop="tp95"
label="95%"
sortable
min-width="60"
/>
<el-table-column
prop="tp99"
label="99%"
sortable
min-width="60"
/>
</el-table-column> </el-table-column>
<el-table-column label="Throughput"> <el-table-column label="Throughput">
@ -124,7 +80,6 @@
width="100" width="100"
/> />
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
</template> </template>
@ -136,13 +91,16 @@ export default {
return { return {
tableData: [], tableData: [],
originalData: [], originalData: [],
id: '', id: "",
searchLabel: '', searchLabel: "",
showSearch: false, showSearch: false,
showBtn: true, showBtn: true,
} };
},
props: ["report", "isShare", "shareId", "planReportTemplate"],
created() {
this.initTableData();
}, },
props: ['report', 'isShare', 'shareId', 'planReportTemplate'],
methods: { methods: {
initTableData() { initTableData() {
if (this.planReportTemplate) { if (this.planReportTemplate) {
@ -152,16 +110,20 @@ export default {
} }
}, },
click(column) { click(column) {
this.searchLabel = ''; this.searchLabel = "";
this.tableData = this.originalData; this.tableData = this.originalData;
this.$set(column, 'showSearch', !column.showSearch); this.$set(column, "showSearch", !column.showSearch);
}, },
filterLabel() { filterLabel() {
this.tableData = this.searchLabel ? this.originalData.filter(this.createFilter(this.searchLabel)) : this.originalData; this.tableData = this.searchLabel
? this.originalData.filter(this.createFilter(this.searchLabel))
: this.originalData;
}, },
createFilter(queryString) { createFilter(queryString) {
return item => { return (item) => {
return (item.label.toLowerCase().indexOf(queryString.toLowerCase()) !== -1); return (
item.label.toLowerCase().indexOf(queryString.toLowerCase()) !== -1
);
}; };
}, },
}, },
@ -179,7 +141,7 @@ export default {
this.tableData = []; this.tableData = [];
} }
}, },
deep: true deep: true,
}, },
planReportTemplate: { planReportTemplate: {
handler() { handler() {
@ -187,14 +149,14 @@ export default {
this.initTableData(); this.initTableData();
} }
}, },
deep: true deep: true,
} },
} },
} };
</script> </script>
<style scoped> <style scoped>
.search_input :deep( .el-input__inner ) { .search_input :deep(.el-input__inner) {
border-radius: 50px; border-radius: 50px;
} }
</style> </style>

View File

@ -1,12 +1,25 @@
<template> <template>
<el-tabs v-model="active"> <el-tabs v-model="active">
<el-tab-pane :label="$t('load_test.pressure_config')"> <el-tab-pane :label="$t('load_test.pressure_config')">
<performance-pressure-config :is-read-only="true" :test="test" :report="report" :report-id="reportId" <performance-pressure-config
:is-share="isShare" :share-id="shareId" @fileChange="fileChange"/> :is-read-only="true"
:test="test"
:report="defaultReport"
:report-id="reportId"
:is-share="isShare"
:share-id="shareId"
@fileChange="fileChange"
/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('load_test.advanced_config')"> <el-tab-pane :label="$t('load_test.advanced_config')">
<performance-advanced-config :is-read-only="true" :report-id="reportId" :report="report" :is-share="isShare" <performance-advanced-config
:share-id="shareId" ref="advancedConfig"/> :is-read-only="true"
:report-id="reportId"
:report="defaultReport"
:is-share="isShare"
:share-id="shareId"
ref="advancedConfig"
/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</template> </template>
@ -17,11 +30,12 @@ import PerformanceAdvancedConfig from "../../../load/PerformanceAdvancedConfig";
export default { export default {
name: "TestConfiguration", name: "TestConfiguration",
components: {PerformancePressureConfig, PerformanceAdvancedConfig}, components: { PerformancePressureConfig, PerformanceAdvancedConfig },
data() { data() {
return { return {
active: '0' active: "0",
} defaultReport: {},
};
}, },
props: { props: {
test: Object, test: Object,
@ -30,26 +44,33 @@ export default {
report: Object, report: Object,
isShare: Boolean, isShare: Boolean,
shareId: String, shareId: String,
planReportTemplate: Object,
},
created() {
if (this.planReportTemplate) {
this.defaultReport = this.planReportTemplate;
} else {
this.defaultReport = this.report;
}
}, },
methods: { methods: {
fileChange(threadGroups) { fileChange(threadGroups) {
let csvSet = new Set; let csvSet = new Set();
threadGroups.forEach(tg => { threadGroups.forEach((tg) => {
if (tg.csvFiles) { if (tg.csvFiles) {
tg.csvFiles.map(item => csvSet.add(item)); tg.csvFiles.map((item) => csvSet.add(item));
} }
}); });
let csvFiles = []; let csvFiles = [];
for (const f of csvSet) { for (const f of csvSet) {
csvFiles.push({name: f, csvSplit: false, csvHasHeader: true}); csvFiles.push({ name: f, csvSplit: false, csvHasHeader: true });
}
if (this.$refs.advancedConfig) {
this.$refs.advancedConfig.csvFiles = csvFiles;
} }
this.$refs.advancedConfig.csvFiles = csvFiles;
}, },
} },
}; };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -2,33 +2,49 @@
<div> <div>
<el-row> <el-row>
<el-col :span="6"> <el-col :span="6">
<div style="padding-bottom: 5px;"> <div style="padding-bottom: 5px">
<el-link type="primary" @click="resetDefault()">{{ $t('load_test.report.set_default') }}</el-link> <el-link type="primary" @click="resetDefault()">{{
$t("load_test.report.set_default")
}}</el-link>
</div> </div>
<el-collapse v-model="activeNames" class="test-detail"> <el-collapse v-model="activeNames" class="test-detail">
<el-collapse-item name="users"> <el-collapse-item name="users">
<template v-slot:title> <template v-slot:title>
<div style="width: 100%"> <div style="width: 100%">
<span>{{ $t('load_test.report.ActiveThreadsChart') }}</span> <span>{{ $t("load_test.report.ActiveThreadsChart") }}</span>
<span style="float:right;"> <span style="float: right">
<el-link type="primary" @click="selectAll( 'ActiveThreadsChart', $event)"> <el-link
{{ $t('load_test.report.select_all') }} type="primary"
@click="selectAll('ActiveThreadsChart', $event)"
>
{{ $t("load_test.report.select_all") }}
</el-link> </el-link>
/ /
<el-link type="default" @click="unselectAll('ActiveThreadsChart', $event)"> <el-link
{{ $t('load_test.report.unselect_all') }} type="default"
@click="unselectAll('ActiveThreadsChart', $event)"
>
{{ $t("load_test.report.unselect_all") }}
</el-link> </el-link>
</span> </span>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkList['ActiveThreadsChart']" <el-checkbox-group
@change="handleChecked('ActiveThreadsChart')"> v-model="checkList['ActiveThreadsChart']"
<div v-for="name in checkOptions['ActiveThreadsChart']" :key="name"> @change="handleChecked('ActiveThreadsChart')"
<el-tooltip class="item" effect="dark" >
:content="name" <div
:disabled="name.length < minLength" v-for="name in checkOptions['ActiveThreadsChart']"
placement="top"> :key="name"
<el-checkbox :label="name"/> >
<el-tooltip
class="item"
effect="dark"
:content="name"
:disabled="name.length < minLength"
placement="top"
>
<el-checkbox :label="name" />
</el-tooltip> </el-tooltip>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
@ -36,27 +52,40 @@
<el-collapse-item name="transactions"> <el-collapse-item name="transactions">
<template v-slot:title> <template v-slot:title>
<div style="width: 100%"> <div style="width: 100%">
<span>{{ $t('load_test.report.TransactionsChart') }}</span> <span>{{ $t("load_test.report.TransactionsChart") }}</span>
<span style="float:right;"> <span style="float: right">
<el-link type="primary" @click="selectAll( 'TransactionsChart', $event)"> <el-link
{{ $t('load_test.report.select_all') }} type="primary"
@click="selectAll('TransactionsChart', $event)"
>
{{ $t("load_test.report.select_all") }}
</el-link> </el-link>
/ /
<el-link type="default" @click="unselectAll('TransactionsChart', $event)"> <el-link
{{ $t('load_test.report.unselect_all') }} type="default"
@click="unselectAll('TransactionsChart', $event)"
>
{{ $t("load_test.report.unselect_all") }}
</el-link> </el-link>
</span> </span>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkList['TransactionsChart']" @change="handleChecked('TransactionsChart')"> <el-checkbox-group
<div v-for="name in checkOptions['TransactionsChart']" v-model="checkList['TransactionsChart']"
:key="name"> @change="handleChecked('TransactionsChart')"
<el-tooltip class="item" effect="dark" >
<div
:content="name" v-for="name in checkOptions['TransactionsChart']"
:disabled="name.length < minLength" :key="name"
placement="top"> >
<el-checkbox :label="name"/> <el-tooltip
class="item"
effect="dark"
:content="name"
:disabled="name.length < minLength"
placement="top"
>
<el-checkbox :label="name" />
</el-tooltip> </el-tooltip>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
@ -64,26 +93,40 @@
<el-collapse-item name="responseTime"> <el-collapse-item name="responseTime">
<template v-slot:title> <template v-slot:title>
<div style="width: 100%"> <div style="width: 100%">
<span>{{ $t('load_test.report.ResponseTimeChart') }}</span> <span>{{ $t("load_test.report.ResponseTimeChart") }}</span>
<span style="float:right;"> <span style="float: right">
<el-link type="primary" @click="selectAll( 'ResponseTimeChart', $event)"> <el-link
{{ $t('load_test.report.select_all') }} type="primary"
@click="selectAll('ResponseTimeChart', $event)"
>
{{ $t("load_test.report.select_all") }}
</el-link> </el-link>
/ /
<el-link type="default" @click="unselectAll('ResponseTimeChart', $event)"> <el-link
{{ $t('load_test.report.unselect_all') }} type="default"
@click="unselectAll('ResponseTimeChart', $event)"
>
{{ $t("load_test.report.unselect_all") }}
</el-link> </el-link>
</span> </span>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkList['ResponseTimeChart']" @change="handleChecked('ResponseTimeChart')"> <el-checkbox-group
<div v-for="name in checkOptions['ResponseTimeChart']" v-model="checkList['ResponseTimeChart']"
:key="name"> @change="handleChecked('ResponseTimeChart')"
<el-tooltip class="item" effect="dark" >
:content="name" <div
:disabled="name.length < minLength" v-for="name in checkOptions['ResponseTimeChart']"
placement="top"> :key="name"
<el-checkbox :label="name"/> >
<el-tooltip
class="item"
effect="dark"
:content="name"
:disabled="name.length < minLength"
placement="top"
>
<el-checkbox :label="name" />
</el-tooltip> </el-tooltip>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
@ -91,136 +134,212 @@
<el-collapse-item name="responseTimePercentiles"> <el-collapse-item name="responseTimePercentiles">
<template v-slot:title> <template v-slot:title>
<div style="width: 100%"> <div style="width: 100%">
<span>{{ $t('load_test.report.ResponseTimePercentilesChart') }}</span> <span>{{
<span style="float:right;"> $t("load_test.report.ResponseTimePercentilesChart")
<el-link type="primary" @click="selectAll( 'ResponseTimePercentilesChart', $event)"> }}</span>
{{ $t('load_test.report.select_all') }} <span style="float: right">
<el-link
type="primary"
@click="selectAll('ResponseTimePercentilesChart', $event)"
>
{{ $t("load_test.report.select_all") }}
</el-link> </el-link>
/ /
<el-link type="default" @click="unselectAll('ResponseTimePercentilesChart', $event)"> <el-link
{{ $t('load_test.report.unselect_all') }} type="default"
@click="unselectAll('ResponseTimePercentilesChart', $event)"
>
{{ $t("load_test.report.unselect_all") }}
</el-link> </el-link>
</span> </span>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkList['ResponseTimePercentilesChart']" <el-checkbox-group
@change="handleChecked('ResponseTimePercentilesChart')"> v-model="checkList['ResponseTimePercentilesChart']"
<div v-for="name in checkOptions['ResponseTimePercentilesChart']" @change="handleChecked('ResponseTimePercentilesChart')"
:key="name"> >
<el-tooltip class="item" effect="dark" <div
:content="name" v-for="name in checkOptions['ResponseTimePercentilesChart']"
:disabled="name.length < minLength" :key="name"
placement="top"> >
<el-checkbox :label="name"/> <el-tooltip
class="item"
effect="dark"
:content="name"
:disabled="name.length < minLength"
placement="top"
>
<el-checkbox :label="name" />
</el-tooltip> </el-tooltip>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
</el-collapse-item> </el-collapse-item>
<el-collapse-item :title="$t('load_test.report.ResponseCodeChart')" name="responseCode"> <el-collapse-item
:title="$t('load_test.report.ResponseCodeChart')"
name="responseCode"
>
<template v-slot:title> <template v-slot:title>
<div style="width: 100%"> <div style="width: 100%">
<span>{{ $t('load_test.report.ResponseCodeChart') }}</span> <span>{{ $t("load_test.report.ResponseCodeChart") }}</span>
<span style="float:right;"> <span style="float: right">
<el-link type="primary" @click="selectAll( 'ResponseCodeChart', $event)"> <el-link
{{ $t('load_test.report.select_all') }} type="primary"
@click="selectAll('ResponseCodeChart', $event)"
>
{{ $t("load_test.report.select_all") }}
</el-link> </el-link>
/ /
<el-link type="default" @click="unselectAll('ResponseCodeChart', $event)"> <el-link
{{ $t('load_test.report.unselect_all') }} type="default"
@click="unselectAll('ResponseCodeChart', $event)"
>
{{ $t("load_test.report.unselect_all") }}
</el-link> </el-link>
</span> </span>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkList['ResponseCodeChart']" @change="handleChecked('ResponseCodeChart')"> <el-checkbox-group
<div v-for="name in checkOptions['ResponseCodeChart']" v-model="checkList['ResponseCodeChart']"
:key="name"> @change="handleChecked('ResponseCodeChart')"
<el-tooltip class="item" effect="dark" >
:content="name" <div
:disabled="name.length < minLength" v-for="name in checkOptions['ResponseCodeChart']"
placement="top"> :key="name"
<el-checkbox :label="name"/> >
<el-tooltip
class="item"
effect="dark"
:content="name"
:disabled="name.length < minLength"
placement="top"
>
<el-checkbox :label="name" />
</el-tooltip> </el-tooltip>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
</el-collapse-item> </el-collapse-item>
<el-collapse-item :title="$t('load_test.report.LatencyChart')" name="latency"> <el-collapse-item
:title="$t('load_test.report.LatencyChart')"
name="latency"
>
<template v-slot:title> <template v-slot:title>
<div style="width: 100%"> <div style="width: 100%">
<span>{{ $t('load_test.report.LatencyChart') }}</span> <span>{{ $t("load_test.report.LatencyChart") }}</span>
<span style="float:right;"> <span style="float: right">
<el-link type="primary" @click="selectAll( 'LatencyChart', $event)"> <el-link
{{ $t('load_test.report.select_all') }} type="primary"
@click="selectAll('LatencyChart', $event)"
>
{{ $t("load_test.report.select_all") }}
</el-link> </el-link>
/ /
<el-link type="default" @click="unselectAll('LatencyChart', $event)"> <el-link
{{ $t('load_test.report.unselect_all') }} type="default"
@click="unselectAll('LatencyChart', $event)"
>
{{ $t("load_test.report.unselect_all") }}
</el-link> </el-link>
</span> </span>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkList['LatencyChart']" @change="handleChecked('LatencyChart')"> <el-checkbox-group
<div v-for="name in checkOptions['LatencyChart']" v-model="checkList['LatencyChart']"
:key="name"> @change="handleChecked('LatencyChart')"
<el-tooltip class="item" effect="dark" >
:content="name" <div v-for="name in checkOptions['LatencyChart']" :key="name">
:disabled="name.length < minLength" <el-tooltip
placement="top"> class="item"
<el-checkbox :label="name"/> effect="dark"
:content="name"
:disabled="name.length < minLength"
placement="top"
>
<el-checkbox :label="name" />
</el-tooltip> </el-tooltip>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
</el-collapse-item> </el-collapse-item>
<el-collapse-item :title="$t('load_test.report.BytesThroughputChart')" name="bytes"> <el-collapse-item
:title="$t('load_test.report.BytesThroughputChart')"
name="bytes"
>
<template v-slot:title> <template v-slot:title>
<div style="width: 100%"> <div style="width: 100%">
<span>{{ $t('load_test.report.BytesThroughputChart') }}</span> <span>{{ $t("load_test.report.BytesThroughputChart") }}</span>
<span style="float:right;"> <span style="float: right">
<el-link type="primary" @click="selectAll( 'BytesThroughputChart', $event)"> <el-link
{{ $t('load_test.report.select_all') }} type="primary"
@click="selectAll('BytesThroughputChart', $event)"
>
{{ $t("load_test.report.select_all") }}
</el-link> </el-link>
/ /
<el-link type="default" @click="unselectAll('BytesThroughputChart', $event)"> <el-link
{{ $t('load_test.report.unselect_all') }} type="default"
@click="unselectAll('BytesThroughputChart', $event)"
>
{{ $t("load_test.report.unselect_all") }}
</el-link> </el-link>
</span> </span>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkList['BytesThroughputChart']" <el-checkbox-group
@change="handleChecked('BytesThroughputChart')"> v-model="checkList['BytesThroughputChart']"
<div v-for="name in checkOptions['BytesThroughputChart']" @change="handleChecked('BytesThroughputChart')"
:key="name"> >
<el-tooltip class="item" effect="dark" <div
:content="name" v-for="name in checkOptions['BytesThroughputChart']"
:disabled="name.length < minLength" :key="name"
placement="top"> >
<el-checkbox :label="name"/> <el-tooltip
class="item"
effect="dark"
:content="name"
:disabled="name.length < minLength"
placement="top"
>
<el-checkbox :label="name" />
</el-tooltip> </el-tooltip>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
</el-collapse-item> </el-collapse-item>
<el-collapse-item :title="$t('load_test.report.ErrorsChart')" name="errors"> <el-collapse-item
:title="$t('load_test.report.ErrorsChart')"
name="errors"
>
<template v-slot:title> <template v-slot:title>
<div style="width: 100%"> <div style="width: 100%">
<span>{{ $t('load_test.report.ErrorsChart') }}</span> <span>{{ $t("load_test.report.ErrorsChart") }}</span>
<span style="float:right;"> <span style="float: right">
<el-link type="primary" @click="selectAll( 'ErrorsChart', $event)"> <el-link
{{ $t('load_test.report.select_all') }} type="primary"
@click="selectAll('ErrorsChart', $event)"
>
{{ $t("load_test.report.select_all") }}
</el-link> </el-link>
/ /
<el-link type="default" @click="unselectAll('ErrorsChart', $event)"> <el-link
{{ $t('load_test.report.unselect_all') }} type="default"
@click="unselectAll('ErrorsChart', $event)"
>
{{ $t("load_test.report.unselect_all") }}
</el-link> </el-link>
</span> </span>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkList['ErrorsChart']" @change="handleChecked('ErrorsChart')"> <el-checkbox-group
<div v-for="name in checkOptions['ErrorsChart']" v-model="checkList['ErrorsChart']"
:key="name"> @change="handleChecked('ErrorsChart')"
<el-tooltip class="item" effect="dark" >
:content="name" <div v-for="name in checkOptions['ErrorsChart']" :key="name">
:disabled="name.length < minLength" <el-tooltip
placement="top"> class="item"
<el-checkbox :label="name"/> effect="dark"
:content="name"
:disabled="name.length < minLength"
placement="top"
>
<el-checkbox :label="name" />
</el-tooltip> </el-tooltip>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
@ -230,12 +349,14 @@
<el-col :span="18" v-loading="result.loading"> <el-col :span="18" v-loading="result.loading">
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<ms-chart ref="chart2" <ms-chart
v-if="refresh" ref="chart2"
class="chart-config" v-if="refresh"
:options="totalOption" class="chart-config"
@datazoom="changeDataZoom" :options="totalOption"
:autoresize="true"/> @datazoom="changeDataZoom"
:autoresize="true"
/>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
@ -245,45 +366,20 @@
:data="tableData" :data="tableData"
stripe stripe
border border
style="width: 100%"> style="width: 100%"
>
<el-table-column label="Label" align="center"> <el-table-column label="Label" align="center">
<el-table-column <el-table-column prop="label" label="Label" sortable>
prop="label"
label="Label"
sortable>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="Aggregate" align="center"> <el-table-column label="Aggregate" align="center">
<el-table-column <el-table-column prop="avg" label="Avg." width="100" sortable />
prop="avg" <el-table-column prop="min" label="Min." width="100" sortable />
label="Avg." <el-table-column prop="max" label="Max." width="100" sortable />
width="100"
sortable
/>
<el-table-column
prop="min"
label="Min."
width="100"
sortable
/>
<el-table-column
prop="max"
label="Max."
width="100"
sortable
/>
</el-table-column> </el-table-column>
<el-table-column label="Range" align="center"> <el-table-column label="Range" align="center">
<el-table-column <el-table-column prop="startTime" label="Start" width="160" />
prop="startTime" <el-table-column prop="endTime" label="End" width="160" />
label="Start"
width="160"
/>
<el-table-column
prop="endTime"
label="End"
width="160"
/>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-col> </el-col>
@ -296,7 +392,19 @@
<script> <script>
import MsChart from "metersphere-frontend/src/components/chart/MsChart"; import MsChart from "metersphere-frontend/src/components/chart/MsChart";
const color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3']; const color = [
"#60acfc",
"#32d3eb",
"#5bc49f",
"#feb64d",
"#ff7c7c",
"#9287e7",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
const groupBy = function (xs, key) { const groupBy = function (xs, key) {
return xs.reduce(function (rv, x) { return xs.reduce(function (rv, x) {
@ -306,24 +414,24 @@ const groupBy = function (xs, key) {
}; };
const CHART_MAP = [ const CHART_MAP = [
'ActiveThreadsChart', "ActiveThreadsChart",
'TransactionsChart', "TransactionsChart",
'ResponseTimeChart', "ResponseTimeChart",
'ResponseTimePercentilesChart', "ResponseTimePercentilesChart",
'ResponseCodeChart', "ResponseCodeChart",
'ErrorsChart', "ErrorsChart",
'LatencyChart', "LatencyChart",
'BytesThroughputChart', "BytesThroughputChart",
]; ];
export default { export default {
name: "TestDetails", name: "TestDetails",
components: {MsChart}, components: { MsChart },
props: ['report', 'export', 'isShare', 'shareId', 'planReportTemplate'], props: ["report", "export", "isShare", "shareId", "planReportTemplate"],
data() { data() {
return { return {
result: {}, result: {},
activeNames: 'users', activeNames: "users",
minLength: 35, minLength: 35,
loadOption: {}, loadOption: {},
resOption: {}, resOption: {},
@ -335,8 +443,8 @@ export default {
}, {}), }, {}),
checkOptions: {}, checkOptions: {},
defaultProps: { defaultProps: {
children: 'children', children: "children",
label: 'label' label: "label",
}, },
init: false, init: false,
refresh: true, refresh: true,
@ -349,9 +457,9 @@ export default {
title: {}, title: {},
tooltip: { tooltip: {
show: true, show: true,
trigger: 'axis', trigger: "axis",
axisPointer: { axisPointer: {
type: 'cross' type: "cross",
}, },
confine: true, confine: true,
formatter: function (params, ticket, callback) { formatter: function (params, ticket, callback) {
@ -369,41 +477,43 @@ export default {
} }
return result; return result;
} },
}, },
legend: { legend: {
y: 'top', y: "top",
}, },
xAxis: {boundaryGap: false}, xAxis: { boundaryGap: false },
yAxis: [], yAxis: [],
dataZoom: [ dataZoom: [
{ {
type: 'inside', type: "inside",
start: 0, start: 0,
end: 100 end: 100,
}, },
{ {
start: 0, start: 0,
end: 20 end: 20,
} },
], ],
series: [] series: [],
}, },
seriesData: [], seriesData: [],
legend: [], legend: [],
}; };
}, },
created() {
this.initTableData();
},
methods: { methods: {
resetDefault() { resetDefault() {
this.checkList["ActiveThreadsChart"] = ["ALL"];
this.checkList['ActiveThreadsChart'] = ['ALL']; this.checkList["TransactionsChart"] = ["ALL"];
this.checkList['TransactionsChart'] = ['ALL']; this.checkList["ResponseTimeChart"] = ["ALL"];
this.checkList['ResponseTimeChart'] = ['ALL'];
// //
this.checkList['ResponseTimePercentilesChart'] = []; this.checkList["ResponseTimePercentilesChart"] = [];
this.checkList['ErrorsChart'] = []; this.checkList["ErrorsChart"] = [];
this.checkList['LatencyChart'] = []; this.checkList["LatencyChart"] = [];
this.checkList['BytesThroughputChart'] = []; this.checkList["BytesThroughputChart"] = [];
this.getTotalChart(); this.getTotalChart();
}, },
@ -437,7 +547,6 @@ export default {
} }
}, },
handleChecked(name) { handleChecked(name) {
this.getTotalChart(); this.getTotalChart();
this.refresh = false; this.refresh = false;
@ -463,9 +572,11 @@ export default {
this.init = false; this.init = false;
return; return;
} }
let yAxisIndex0List = data.filter(m => m.yAxis2 === -1).map(m => m.groupName); let yAxisIndex0List = data
.filter((m) => m.yAxis2 === -1)
.map((m) => m.groupName);
yAxisIndex0List = this._unique(yAxisIndex0List); yAxisIndex0List = this._unique(yAxisIndex0List);
this.checkOptions[reportKey] = ['ALL'].concat(yAxisIndex0List); this.checkOptions[reportKey] = ["ALL"].concat(yAxisIndex0List);
}, },
getTotalChart() { getTotalChart() {
this.result.loading = true; this.result.loading = true;
@ -479,22 +590,24 @@ export default {
let chars = []; let chars = [];
for (let name in this.checkList) { for (let name in this.checkList) {
let data = this.planReportTemplate.checkOptions[name]; let data = this.planReportTemplate.checkOptions[name];
chars.push({data, 'reportKey': name}); chars.push({ data, reportKey: name });
} }
this.handleGetTotalChart(chars); this.handleGetTotalChart(chars);
} else { } else {
for (let name in this.checkList) { for (let name in this.checkList) {
promises.push(this.getChart(name, this.checkList[name])); promises.push(this.getChart(name, this.checkList[name]));
} }
Promise.all(promises).then((res) => { Promise.all(promises)
this.handleGetTotalChart(res); .then((res) => {
}).catch(() => { this.handleGetTotalChart(res);
this.result.loading = false; })
}); .catch(() => {
this.result.loading = false;
});
} }
}, },
handleGetTotalChart(res) { handleGetTotalChart(res) {
res = res.filter(v => !!v); res = res.filter((v) => !!v);
if (res.length === 0) { if (res.length === 0) {
this.refresh = false; this.refresh = false;
this.result.loading = false; this.result.loading = false;
@ -504,27 +617,27 @@ export default {
for (let i = 0; i < res.length; i++) { for (let i = 0; i < res.length; i++) {
if (i === 0) { if (i === 0) {
this.baseOption.yAxis.push({ this.baseOption.yAxis.push({
name: this.$t('load_test.report.' + res[i].reportKey), name: this.$t("load_test.report." + res[i].reportKey),
type: 'value', type: "value",
min: 0, min: 0,
position: 'left', position: "left",
boundaryGap: [0, '100%'] boundaryGap: [0, "100%"],
}); });
} else { } else {
this.baseOption.yAxis.push({ this.baseOption.yAxis.push({
name: this.$t('load_test.report.' + res[i].reportKey), name: this.$t("load_test.report." + res[i].reportKey),
type: 'value', type: "value",
min: 0, min: 0,
position: 'right', position: "right",
nameRotate: 20, nameRotate: 20,
offset: (i - 1) * 50, offset: (i - 1) * 50,
boundaryGap: [0, '100%'] boundaryGap: [0, "100%"],
}); });
} }
this.totalOption = this.generateOption(this.baseOption, res[i].data, i); this.totalOption = this.generateOption(this.baseOption, res[i].data, i);
} }
this.totalOption.grid.right = (res.length - 1) * 5 + '%'; this.totalOption.grid.right = (res.length - 1) * 5 + "%";
this.changeDataZoom({start: 0, end: 100}); this.changeDataZoom({ start: 0, end: 100 });
this.result.loading = false; this.result.loading = false;
}, },
getChart(reportKey, checkList) { getChart(reportKey, checkList) {
@ -535,29 +648,31 @@ export default {
}, },
handleGetChart(data, reportKey, checkList) { handleGetChart(data, reportKey, checkList) {
let allData = []; let allData = [];
let checkAllOption = checkList.indexOf('ALL') > -1; let checkAllOption = checkList.indexOf("ALL") > -1;
if (checkAllOption) { if (checkAllOption) {
let avgOpt = [ let avgOpt = [
'ResponseTimeChart', "ResponseTimeChart",
'ResponseTimePercentilesChart', "ResponseTimePercentilesChart",
'LatencyChart', "LatencyChart",
]; ];
let result = groupBy(data, 'xAxis'); let result = groupBy(data, "xAxis");
for (const xAxis in result) { for (const xAxis in result) {
let yAxis = result[xAxis].map(a => a.yAxis).reduce((a, b) => a + b, 0); let yAxis = result[xAxis]
.map((a) => a.yAxis)
.reduce((a, b) => a + b, 0);
if (avgOpt.indexOf(reportKey) > -1) { if (avgOpt.indexOf(reportKey) > -1) {
yAxis = yAxis / result[xAxis].length; yAxis = yAxis / result[xAxis].length;
} }
allData.push({ allData.push({
groupName: 'ALL', groupName: "ALL",
xAxis: xAxis, xAxis: xAxis,
yAxis: yAxis yAxis: yAxis,
}); });
} }
} }
// //
data = data.filter(item => { data = data.filter((item) => {
if (checkList.indexOf(item.groupName) > -1) { if (checkList.indexOf(item.groupName) > -1) {
return true; return true;
} }
@ -566,17 +681,18 @@ export default {
// all // all
data = data.concat(allData); data = data.concat(allData);
// prefix // prefix
data.forEach(item => { data.forEach((item) => {
item.groupName = this.$t('load_test.report.' + reportKey) + ': ' + item.groupName; item.groupName =
this.$t("load_test.report." + reportKey) + ": " + item.groupName;
}); });
return {data, reportKey}; return { data, reportKey };
}, },
generateOption(option, data, yAxisIndex) { generateOption(option, data, yAxisIndex) {
let chartData = data; let chartData = data;
let series = {}, xAxis = []; let series = {},
chartData.forEach(item => { xAxis = [];
chartData.forEach((item) => {
if (!xAxis.includes(item.xAxis)) { if (!xAxis.includes(item.xAxis)) {
xAxis.push(item.xAxis); xAxis.push(item.xAxis);
} }
@ -587,9 +703,11 @@ export default {
series[name] = []; series[name] = [];
} }
if (series[name]) { if (series[name]) {
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis.toFixed(2)]); series[name].splice(xAxis.indexOf(item.xAxis), 0, [
item.xAxis,
item.yAxis.toFixed(2),
]);
} }
}); });
this.$set(option.legend, "data", this.legend); this.$set(option.legend, "data", this.legend);
this.$set(option.legend, "type", "scroll"); this.$set(option.legend, "type", "scroll");
@ -600,11 +718,11 @@ export default {
d.sort((a, b) => a[0].localeCompare(b[0])); d.sort((a, b) => a[0].localeCompare(b[0]));
let items = { let items = {
name: name, name: name,
type: 'line', type: "line",
data: d, data: d,
yAxisIndex: yAxisIndex, yAxisIndex: yAxisIndex,
smooth: true, smooth: true,
sampling: 'lttb', sampling: "lttb",
showSymbol: false, showSymbol: false,
animation: !this.export, animation: !this.export,
}; };
@ -623,9 +741,15 @@ export default {
let tableData = []; let tableData = [];
for (let i = 0; i < this.seriesData.length; i++) { for (let i = 0; i < this.seriesData.length; i++) {
let sub = this.seriesData[i].data, label = this.seriesData[i].name; let sub = this.seriesData[i].data,
label = this.seriesData[i].name;
let len = 0; let len = 0;
let min, avg, max, sum = 0, startTime, endTime; let min,
avg,
max,
sum = 0,
startTime,
endTime;
for (let j = 0; j < sub.length; j++) { for (let j = 0; j < sub.length; j++) {
let time = sub[j][0]; let time = sub[j][0];
let value = Number.parseFloat(sub[j][1]); let value = Number.parseFloat(sub[j][1]);
@ -658,7 +782,7 @@ export default {
} }
avg = (sum / len).toFixed(2); avg = (sum / len).toFixed(2);
tableData.push({label, min, max, avg, startTime, endTime}); tableData.push({ label, min, max, avg, startTime, endTime });
} }
this.tableData = tableData; this.tableData = tableData;
}, },
@ -668,12 +792,12 @@ export default {
}, },
_unique(arr) { _unique(arr) {
return Array.from(new Set(arr)); return Array.from(new Set(arr));
} },
}, },
watch: { watch: {
'$route'(to) { $route(to) {
if (to.name === "perReportView") { if (to.name === "perReportView") {
this.id = to.path.split('/')[4]; this.id = to.path.split("/")[4];
this.init = false; this.init = false;
this.initTableData(); this.initTableData();
} }
@ -694,7 +818,7 @@ export default {
this.initTableData(); this.initTableData();
} }
}, },
deep: true deep: true,
}, },
planReportTemplate: { planReportTemplate: {
handler() { handler() {
@ -704,10 +828,9 @@ export default {
// this.getTotalChart(); // this.getTotalChart();
} }
}, },
deep: true deep: true,
} },
}, },
}; };
</script> </script>
@ -722,7 +845,7 @@ export default {
overflow: auto; overflow: auto;
} }
:deep(.el-checkbox__label ) { :deep(.el-checkbox__label) {
font-size: 10px !important; font-size: 10px !important;
} }
</style> </style>

View File

@ -7,7 +7,9 @@
<span class="ms-card-data-digital">{{ maxUsers }}</span> <span class="ms-card-data-digital">{{ maxUsers }}</span>
<span class="ms-card-data-unit"> VU</span> <span class="ms-card-data-unit"> VU</span>
</span> </span>
<span class="ms-card-desc">{{ $t('load_test.report.ActiveThreadsChart') }}</span> <span class="ms-card-desc">{{
$t("load_test.report.ActiveThreadsChart")
}}</span>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
@ -16,7 +18,9 @@
<span class="ms-card-data-digital">{{ avgTransactions }}</span> <span class="ms-card-data-digital">{{ avgTransactions }}</span>
<span class="ms-card-data-unit"> TPS</span> <span class="ms-card-data-unit"> TPS</span>
</span> </span>
<span class="ms-card-desc">{{ $t('load_test.report.TransactionsChart') }}</span> <span class="ms-card-desc">{{
$t("load_test.report.TransactionsChart")
}}</span>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
@ -25,7 +29,9 @@
<span class="ms-card-data-digital">{{ errors }}</span> <span class="ms-card-data-digital">{{ errors }}</span>
<span class="ms-card-data-unit"> %</span> <span class="ms-card-data-unit"> %</span>
</span> </span>
<span class="ms-card-desc">{{ $t('load_test.report.ErrorsChart') }}</span> <span class="ms-card-desc">{{
$t("load_test.report.ErrorsChart")
}}</span>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
@ -34,7 +40,9 @@
<span class="ms-card-data-digital">{{ avgResponseTime }}</span> <span class="ms-card-data-digital">{{ avgResponseTime }}</span>
<span class="ms-card-data-unit"> s</span> <span class="ms-card-data-unit"> s</span>
</span> </span>
<span class="ms-card-desc">{{ $t('load_test.report.ResponseTimeChart') }}</span> <span class="ms-card-desc">{{
$t("load_test.report.ResponseTimeChart")
}}</span>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
@ -43,7 +51,9 @@
<span class="ms-card-data-digital">{{ responseTime90 }}</span> <span class="ms-card-data-digital">{{ responseTime90 }}</span>
<span class="ms-card-data-unit"> s</span> <span class="ms-card-data-unit"> s</span>
</span> </span>
<span class="ms-card-desc">90% {{ $t('load_test.report.ResponseTimeChart') }}</span> <span class="ms-card-desc"
>90% {{ $t("load_test.report.ResponseTimeChart") }}</span
>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
@ -52,17 +62,27 @@
<span class="ms-card-data-digital">{{ avgBandwidth }}</span> <span class="ms-card-data-digital">{{ avgBandwidth }}</span>
<span class="ms-card-data-unit"> KiB/s</span> <span class="ms-card-data-unit"> KiB/s</span>
</span> </span>
<span class="ms-card-desc">{{ $t('load_test.report.Network') }}</span> <span class="ms-card-desc">{{ $t("load_test.report.Network") }}</span>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<ms-chart ref="chart1" :options="loadOption" class="chart-config" :autoresize="true"></ms-chart> <ms-chart
ref="chart1"
:options="loadOption"
class="chart-config"
:autoresize="true"
></ms-chart>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<ms-chart ref="chart2" :options="resOption" class="chart-config" :autoresize="true"></ms-chart> <ms-chart
ref="chart2"
:options="resOption"
class="chart-config"
:autoresize="true"
></ms-chart>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
@ -71,7 +91,19 @@
<script> <script>
import MsChart from "metersphere-frontend/src/components/chart/MsChart"; import MsChart from "metersphere-frontend/src/components/chart/MsChart";
const color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3']; const color = [
"#60acfc",
"#32d3eb",
"#5bc49f",
"#feb64d",
"#ff7c7c",
"#9287e7",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
const groupBy = function (xs, key) { const groupBy = function (xs, key) {
return xs.reduce(function (rv, x) { return xs.reduce(function (rv, x) {
@ -82,7 +114,7 @@ const groupBy = function (xs, key) {
export default { export default {
name: "TestOverview", name: "TestOverview",
components: {MsChart}, components: { MsChart },
data() { data() {
return { return {
maxUsers: "0", maxUsers: "0",
@ -96,10 +128,13 @@ export default {
resOption: {}, resOption: {},
errorOption: {}, errorOption: {},
resCodeOption: {}, resCodeOption: {},
id: '' id: "",
}; };
}, },
props: ['report', 'export', 'isShare', 'shareId', 'planReportTemplate'], props: ["report", "export", "isShare", "shareId", "planReportTemplate"],
created() {
this.initTableData();
},
methods: { methods: {
initTableData() { initTableData() {
if (this.planReportTemplate) { if (this.planReportTemplate) {
@ -110,13 +145,13 @@ export default {
this.getResChart(); this.getResChart();
}, },
buildInfo(data) { buildInfo(data) {
this.maxUsers = data ? data.maxUsers : '0'; this.maxUsers = data ? data.maxUsers : "0";
this.avgThroughput = data ? data.avgThroughput : '0'; this.avgThroughput = data ? data.avgThroughput : "0";
this.avgTransactions = data ? data.avgTransactions : '0'; this.avgTransactions = data ? data.avgTransactions : "0";
this.errors = data ? data.errors : '0'; this.errors = data ? data.errors : "0";
this.avgResponseTime = data ? data.avgResponseTime : '0'; this.avgResponseTime = data ? data.avgResponseTime : "0";
this.responseTime90 = data ? data.responseTime90 : '0'; this.responseTime90 = data ? data.responseTime90 : "0";
this.avgBandwidth = data ? data.avgBandwidth : '0'; this.avgBandwidth = data ? data.avgBandwidth : "0";
}, },
getLoadChart() { getLoadChart() {
if (this.planReportTemplate) { if (this.planReportTemplate) {
@ -131,63 +166,77 @@ export default {
let loadOption = { let loadOption = {
color: color, color: color,
title: { title: {
text: 'Load', text: "Load",
left: 'center', left: "center",
top: 20, top: 20,
textStyle: { textStyle: {
color: '#65A2FF' color: "#65A2FF",
}, },
}, },
tooltip: { tooltip: {
show: true, show: true,
trigger: 'axis', trigger: "axis",
// extraCssText: 'z-index: 999;', // extraCssText: 'z-index: 999;',
confine: true, confine: true,
}, },
legend: {}, legend: {},
xAxis: {}, xAxis: {},
series: [] series: [],
}; };
let allData = []; let allData = [];
let result = groupBy(data, 'xAxis'); let result = groupBy(data, "xAxis");
for (const xAxis in result) { for (const xAxis in result) {
let yAxis1 = result[xAxis].filter(a => a.yAxis2 === -1).map(a => a.yAxis).reduce((a, b) => a + b, 0); let yAxis1 = result[xAxis]
let yAxis2 = result[xAxis].filter(a => a.yAxis === -1).map(a => a.yAxis2).reduce((a, b) => a + b, 0); .filter((a) => a.yAxis2 === -1)
allData.push({ .map((a) => a.yAxis)
groupName: 'users', .reduce((a, b) => a + b, 0);
xAxis: xAxis, let yAxis2 = result[xAxis]
yAxis: yAxis1, .filter((a) => a.yAxis === -1)
yAxis2: -1, .map((a) => a.yAxis2)
yAxisIndex: 0, .reduce((a, b) => a + b, 0);
}, { allData.push(
groupName: 'transactions/s', {
xAxis: xAxis, groupName: "users",
yAxis: -1, xAxis: xAxis,
yAxis2: yAxis2, yAxis: yAxis1,
yAxisIndex: 1, yAxis2: -1,
}); yAxisIndex: 0,
},
{
groupName: "transactions/s",
xAxis: xAxis,
yAxis: -1,
yAxis2: yAxis2,
yAxisIndex: 1,
}
);
} }
let yAxisList = allData.filter(m => m.yAxis2 === -1).map(m => m.yAxis); let yAxisList = allData
let yAxis2List = allData.filter(m => m.yAxis === -1).map(m => m.yAxis2); .filter((m) => m.yAxis2 === -1)
.map((m) => m.yAxis);
let yAxis2List = allData
.filter((m) => m.yAxis === -1)
.map((m) => m.yAxis2);
let yAxisListMax = this._getChartMax(yAxisList); let yAxisListMax = this._getChartMax(yAxisList);
let yAxis2ListMax = this._getChartMax(yAxis2List); let yAxis2ListMax = this._getChartMax(yAxis2List);
loadOption.yAxis = [{ loadOption.yAxis = [
name: 'User',
type: 'value',
min: 0,
max: yAxisListMax,
splitNumber: 5,
interval: yAxisListMax / 5
},
{ {
name: 'Transactions/s', name: "User",
type: 'value', type: "value",
min: 0,
max: yAxisListMax,
splitNumber: 5,
interval: yAxisListMax / 5,
},
{
name: "Transactions/s",
type: "value",
splitNumber: 5, splitNumber: 5,
min: 0, min: 0,
max: yAxis2ListMax, max: yAxis2ListMax,
interval: yAxis2ListMax / 5 interval: yAxis2ListMax / 5,
} },
]; ];
this.loadOption = this.generateOption(loadOption, allData); this.loadOption = this.generateOption(loadOption, allData);
}, },
@ -204,16 +253,16 @@ export default {
let resOption = { let resOption = {
color: color, color: color,
title: { title: {
text: 'Response Time', text: "Response Time",
left: 'center', left: "center",
top: 20, top: 20,
textStyle: { textStyle: {
color: '#99743C' color: "#99743C",
}, },
}, },
tooltip: { tooltip: {
show: true, show: true,
trigger: 'axis', trigger: "axis",
// extraCssText: 'z-index: 999;', // extraCssText: 'z-index: 999;',
confine: true, confine: true,
formatter: function (params, ticket, callback) { formatter: function (params, ticket, callback) {
@ -231,21 +280,24 @@ export default {
} }
return result; return result;
} },
}, },
legend: {}, legend: {},
xAxis: {}, xAxis: {},
series: [] series: [],
}; };
let allData = []; let allData = [];
let result = groupBy(data, 'xAxis'); let result = groupBy(data, "xAxis");
for (const xAxis in result) { for (const xAxis in result) {
let yAxis1 = result[xAxis].filter(a => a.yAxis2 === -1).map(a => a.yAxis).reduce((a, b) => a + b, 0); let yAxis1 = result[xAxis]
.filter((a) => a.yAxis2 === -1)
.map((a) => a.yAxis)
.reduce((a, b) => a + b, 0);
yAxis1 = yAxis1 / result[xAxis].length; yAxis1 = yAxis1 / result[xAxis].length;
allData.push({ allData.push({
groupName: 'response', groupName: "response",
xAxis: xAxis, xAxis: xAxis,
yAxis: -1, yAxis: -1,
yAxis2: yAxis1, yAxis2: yAxis1,
@ -253,16 +305,18 @@ export default {
}); });
} }
let yAxisList = allData.filter(m => m.yAxis === -1).map(m => m.yAxis2); let yAxisList = allData
.filter((m) => m.yAxis === -1)
.map((m) => m.yAxis2);
let yAxisListMax = this._getChartMax(yAxisList); let yAxisListMax = this._getChartMax(yAxisList);
resOption.yAxis = [ resOption.yAxis = [
{ {
name: 'Response Time', name: "Response Time",
type: 'value', type: "value",
min: 0, min: 0,
max: yAxisListMax, max: yAxisListMax,
interval: yAxisListMax / 5 interval: yAxisListMax / 5,
} },
]; ];
this.resOption = this.generateOption(resOption, allData); this.resOption = this.generateOption(resOption, allData);
}, },
@ -279,16 +333,16 @@ export default {
let errorOption = { let errorOption = {
color: color, color: color,
title: { title: {
text: 'Errors', text: "Errors",
left: 'center', left: "center",
top: 20, top: 20,
textStyle: { textStyle: {
color: '#99743C' color: "#99743C",
}, },
}, },
tooltip: { tooltip: {
show: true, show: true,
trigger: 'axis', trigger: "axis",
// extraCssText: 'z-index: 999;', // extraCssText: 'z-index: 999;',
confine: true, confine: true,
formatter: function (params, ticket, callback) { formatter: function (params, ticket, callback) {
@ -306,36 +360,41 @@ export default {
} }
return result; return result;
} },
}, },
legend: {}, legend: {},
xAxis: {}, xAxis: {},
series: [] series: [],
}; };
let allData = []; let allData = [];
let result = groupBy(data, 'xAxis'); let result = groupBy(data, "xAxis");
for (const xAxis in result) { for (const xAxis in result) {
let yAxis1 = result[xAxis].filter(a => a.yAxis2 === -1).map(a => a.yAxis).reduce((a, b) => a + b, 0); let yAxis1 = result[xAxis]
.filter((a) => a.yAxis2 === -1)
.map((a) => a.yAxis)
.reduce((a, b) => a + b, 0);
allData.push({ allData.push({
groupName: 'errors', groupName: "errors",
xAxis: xAxis, xAxis: xAxis,
yAxis: -1, yAxis: -1,
yAxis2: yAxis1, yAxis2: yAxis1,
yAxisIndex: 0, yAxisIndex: 0,
}); });
} }
let yAxisList = allData.filter(m => m.yAxis === -1).map(m => m.yAxis2); let yAxisList = allData
.filter((m) => m.yAxis === -1)
.map((m) => m.yAxis2);
let yAxisListMax = this._getChartMax(yAxisList); let yAxisListMax = this._getChartMax(yAxisList);
errorOption.yAxis = [ errorOption.yAxis = [
{ {
name: 'No', name: "No",
type: 'value', type: "value",
min: 0, min: 0,
max: yAxisListMax, max: yAxisListMax,
interval: yAxisListMax / 5 interval: yAxisListMax / 5,
} },
]; ];
this.errorOption = this.generateOption(errorOption, allData); this.errorOption = this.generateOption(errorOption, allData);
@ -353,16 +412,16 @@ export default {
let resCodeOption = { let resCodeOption = {
color: color, color: color,
title: { title: {
text: 'Response code', text: "Response code",
left: 'center', left: "center",
top: 20, top: 20,
textStyle: { textStyle: {
color: '#99743C' color: "#99743C",
}, },
}, },
tooltip: { tooltip: {
show: true, show: true,
trigger: 'axis', trigger: "axis",
// extraCssText: 'z-index: 999;', // extraCssText: 'z-index: 999;',
confine: true, confine: true,
formatter: function (params, ticket, callback) { formatter: function (params, ticket, callback) {
@ -380,43 +439,52 @@ export default {
} }
return result; return result;
} },
}, },
legend: {}, legend: {},
xAxis: {}, xAxis: {},
series: [] series: [],
}; };
let allData = []; let allData = [];
let result = groupBy(data, 'xAxis'); let result = groupBy(data, "xAxis");
for (const xAxis in result) { for (const xAxis in result) {
let yAxis1 = result[xAxis].filter(a => a.yAxis2 === -1).map(a => a.yAxis).reduce((a, b) => a + b, 0); let yAxis1 = result[xAxis]
.filter((a) => a.yAxis2 === -1)
.map((a) => a.yAxis)
.reduce((a, b) => a + b, 0);
allData.push({ allData.push({
groupName: 'codes', groupName: "codes",
xAxis: xAxis, xAxis: xAxis,
yAxis: -1, yAxis: -1,
yAxis2: yAxis1, yAxis2: yAxis1,
yAxisIndex: 0, yAxisIndex: 0,
}); });
} }
let yAxisList = allData.filter(m => m.yAxis === -1).map(m => m.yAxis2); let yAxisList = allData
.filter((m) => m.yAxis === -1)
.map((m) => m.yAxis2);
let yAxisListMax = this._getChartMax(yAxisList); let yAxisListMax = this._getChartMax(yAxisList);
resCodeOption.yAxis = [ resCodeOption.yAxis = [
{ {
name: 'No', name: "No",
type: 'value', type: "value",
min: 0, min: 0,
max: yAxisListMax, max: yAxisListMax,
interval: yAxisListMax / 5 interval: yAxisListMax / 5,
} },
]; ];
this.resCodeOption = this.generateOption(resCodeOption, allData); this.resCodeOption = this.generateOption(resCodeOption, allData);
}, },
generateOption(option, data) { generateOption(option, data) {
let chartData = data; let chartData = data;
let legend = [], series = {}, xAxis = [], seriesData = [], yAxisIndex = {}; let legend = [],
chartData.forEach(item => { series = {},
xAxis = [],
seriesData = [],
yAxisIndex = {};
chartData.forEach((item) => {
if (!xAxis.includes(item.xAxis)) { if (!xAxis.includes(item.xAxis)) {
xAxis.push(item.xAxis); xAxis.push(item.xAxis);
} }
@ -428,9 +496,15 @@ export default {
series[name] = []; series[name] = [];
} }
if (item.yAxis === -1) { if (item.yAxis === -1) {
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis2.toFixed(2)]); series[name].splice(xAxis.indexOf(item.xAxis), 0, [
item.xAxis,
item.yAxis2.toFixed(2),
]);
} else { } else {
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis.toFixed(2)]); series[name].splice(xAxis.indexOf(item.xAxis), 0, [
item.xAxis,
item.yAxis.toFixed(2),
]);
} }
}); });
this.$set(option.legend, "data", legend); this.$set(option.legend, "data", legend);
@ -442,13 +516,13 @@ export default {
d.sort((a, b) => a[0].localeCompare(b[0])); d.sort((a, b) => a[0].localeCompare(b[0]));
let items = { let items = {
name: name, name: name,
type: 'line', type: "line",
data: d, data: d,
smooth: true, smooth: true,
sampling: 'lttb', sampling: "lttb",
showSymbol: false, showSymbol: false,
animation: !this.export, animation: !this.export,
yAxisIndex: yAxisIndex[name] yAxisIndex: yAxisIndex[name],
}; };
seriesData.push(items); seriesData.push(items);
} }
@ -461,7 +535,7 @@ export default {
}, },
_unique(arr) { _unique(arr) {
return Array.from(new Set(arr)); return Array.from(new Set(arr));
} },
}, },
watch: { watch: {
report: { report: {
@ -474,20 +548,20 @@ export default {
if (status === "Completed" || status === "Running") { if (status === "Completed" || status === "Running") {
this.initTableData(); this.initTableData();
} else { } else {
this.maxUsers = '0'; this.maxUsers = "0";
this.avgThroughput = '0'; this.avgThroughput = "0";
this.avgTransactions = '0'; this.avgTransactions = "0";
this.errors = '0'; this.errors = "0";
this.avgResponseTime = '0'; this.avgResponseTime = "0";
this.responseTime90 = '0'; this.responseTime90 = "0";
this.avgBandwidth = '0'; this.avgBandwidth = "0";
this.loadOption = {}; this.loadOption = {};
this.resOption = {}; this.resOption = {};
this.errorOption = {}; this.errorOption = {};
this.resCodeOption = {}; this.resCodeOption = {};
} }
}, },
deep: true deep: true,
}, },
planReportTemplate: { planReportTemplate: {
handler() { handler() {
@ -495,14 +569,13 @@ export default {
this.initTableData(); this.initTableData();
} }
}, },
deep: true deep: true,
} },
}, },
}; };
</script> </script>
<style scoped> <style scoped>
.ms-card-data { .ms-card-data {
text-align: left; text-align: left;
display: block; display: block;
@ -533,52 +606,51 @@ export default {
} }
.ms-card-index-2 .ms-card-data-digital { .ms-card-index-2 .ms-card-data-digital {
color: #65A2FF; color: #65a2ff;
} }
.ms-card-index-2 { .ms-card-index-2 {
border-left-color: #65A2FF; border-left-color: #65a2ff;
border-left-width: 3px; border-left-width: 3px;
} }
.ms-card-index-3 .ms-card-data-digital { .ms-card-index-3 .ms-card-data-digital {
color: #E6113C; color: #e6113c;
} }
.ms-card-index-3 { .ms-card-index-3 {
border-left-color: #E6113C; border-left-color: #e6113c;
border-left-width: 3px; border-left-width: 3px;
} }
.ms-card-index-4 .ms-card-data-digital { .ms-card-index-4 .ms-card-data-digital {
color: #99743C; color: #99743c;
} }
.ms-card-index-4 { .ms-card-index-4 {
border-left-color: #99743C; border-left-color: #99743c;
border-left-width: 3px; border-left-width: 3px;
} }
.ms-card-index-5 .ms-card-data-digital { .ms-card-index-5 .ms-card-data-digital {
color: #99743C; color: #99743c;
} }
.ms-card-index-5 { .ms-card-index-5 {
border-left-color: #99743C; border-left-color: #99743c;
border-left-width: 3px; border-left-width: 3px;
} }
.ms-card-index-6 .ms-card-data-digital { .ms-card-index-6 .ms-card-data-digital {
color: #3C9899; color: #3c9899;
} }
.ms-card-index-6 { .ms-card-index-6 {
border-left-color: #3C9899; border-left-color: #3c9899;
border-left-width: 3px; border-left-width: 3px;
} }
.chart-config { .chart-config {
width: 100%; width: 100%;
} }
</style> </style>