Merge branch 'master' of https://github.com/metersphere/server into v1.3
This commit is contained in:
commit
b98c21b009
|
@ -12,6 +12,7 @@ public class KeyValue {
|
||||||
private String type;
|
private String type;
|
||||||
private List<BodyFile> files;
|
private List<BodyFile> files;
|
||||||
private String description;
|
private String description;
|
||||||
|
private String contentType;
|
||||||
private boolean enable;
|
private boolean enable;
|
||||||
|
|
||||||
public KeyValue() {
|
public KeyValue() {
|
||||||
|
|
|
@ -3,11 +3,11 @@ package io.metersphere.api.jmeter;
|
||||||
import io.metersphere.api.service.APIReportService;
|
import io.metersphere.api.service.APIReportService;
|
||||||
import io.metersphere.api.service.APITestService;
|
import io.metersphere.api.service.APITestService;
|
||||||
import io.metersphere.base.domain.ApiTestReport;
|
import io.metersphere.base.domain.ApiTestReport;
|
||||||
import io.metersphere.base.domain.NoticeDetail;
|
|
||||||
import io.metersphere.commons.constants.APITestStatus;
|
import io.metersphere.commons.constants.APITestStatus;
|
||||||
import io.metersphere.commons.constants.ApiRunMode;
|
import io.metersphere.commons.constants.ApiRunMode;
|
||||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||||
import io.metersphere.commons.utils.LogUtil;
|
import io.metersphere.commons.utils.LogUtil;
|
||||||
|
import io.metersphere.notice.domain.NoticeDetail;
|
||||||
import io.metersphere.notice.service.MailService;
|
import io.metersphere.notice.service.MailService;
|
||||||
import io.metersphere.notice.service.NoticeService;
|
import io.metersphere.notice.service.NoticeService;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -141,6 +141,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
||||||
requestResult.setBody(result.getSamplerData());
|
requestResult.setBody(result.getSamplerData());
|
||||||
requestResult.setHeaders(result.getRequestHeaders());
|
requestResult.setHeaders(result.getRequestHeaders());
|
||||||
requestResult.setRequestSize(result.getSentBytes());
|
requestResult.setRequestSize(result.getSentBytes());
|
||||||
|
requestResult.setStartTime(result.getStartTime());
|
||||||
requestResult.setTotalAssertions(result.getAssertionResults().length);
|
requestResult.setTotalAssertions(result.getAssertionResults().length);
|
||||||
requestResult.setSuccess(result.isSuccessful());
|
requestResult.setSuccess(result.isSuccessful());
|
||||||
requestResult.setError(result.getErrorCount());
|
requestResult.setError(result.getErrorCount());
|
||||||
|
|
|
@ -16,6 +16,8 @@ public class RequestResult {
|
||||||
|
|
||||||
private long requestSize;
|
private long requestSize;
|
||||||
|
|
||||||
|
private long startTime;
|
||||||
|
|
||||||
private int error;
|
private int error;
|
||||||
|
|
||||||
private boolean success;
|
private boolean success;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.metersphere.controller;
|
package io.metersphere.controller;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import io.metersphere.base.domain.User;
|
||||||
import io.metersphere.commons.utils.SessionUtils;
|
import io.metersphere.commons.utils.SessionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
@ -15,7 +16,8 @@ public class TestController {
|
||||||
|
|
||||||
|
|
||||||
@PostMapping(value = "/upload", consumes = {"multipart/form-data"})
|
@PostMapping(value = "/upload", consumes = {"multipart/form-data"})
|
||||||
public Object testUpload(@RequestPart(value = "id") String id, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
public Object testUpload(@RequestPart(value = "id") String id, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles
|
||||||
|
, @RequestPart(value = "user") User user, @RequestParam(value = "name") String name) {
|
||||||
JSONObject jsonObject = new JSONObject();
|
JSONObject jsonObject = new JSONObject();
|
||||||
jsonObject.put("id", id);
|
jsonObject.put("id", id);
|
||||||
jsonObject.put("file", file.getOriginalFilename());
|
jsonObject.put("file", file.getOriginalFilename());
|
||||||
|
|
|
@ -14,7 +14,6 @@ import org.quartz.TriggerKey;
|
||||||
public class ApiTestJob extends MsScheduleJob {
|
public class ApiTestJob extends MsScheduleJob {
|
||||||
|
|
||||||
private APITestService apiTestService;
|
private APITestService apiTestService;
|
||||||
private MailService mailService;
|
|
||||||
public ApiTestJob() {
|
public ApiTestJob() {
|
||||||
apiTestService = (APITestService) CommonBeanFactory.getBean(APITestService.class);
|
apiTestService = (APITestService) CommonBeanFactory.getBean(APITestService.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package io.metersphere.notice.controller;
|
package io.metersphere.notice.controller;
|
||||||
|
|
||||||
import io.metersphere.base.domain.NoticeDetail;
|
|
||||||
import io.metersphere.notice.controller.request.NoticeRequest;
|
import io.metersphere.notice.controller.request.NoticeRequest;
|
||||||
|
import io.metersphere.notice.domain.NoticeDetail;
|
||||||
import io.metersphere.notice.service.NoticeService;
|
import io.metersphere.notice.service.NoticeService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.metersphere.notice.controller.request;
|
package io.metersphere.notice.controller.request;
|
||||||
|
|
||||||
import io.metersphere.base.domain.NoticeDetail;
|
import io.metersphere.notice.domain.NoticeDetail;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package io.metersphere.notice.domain;
|
|
||||||
|
|
||||||
import io.metersphere.base.domain.Notice;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class NoticeDTO extends Notice {
|
|
||||||
private String[] names;
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.metersphere.base.domain;
|
package io.metersphere.notice.domain;
|
||||||
|
|
||||||
|
import io.metersphere.base.domain.Notice;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
|
@ -1,7 +1,6 @@
|
||||||
package io.metersphere.notice.service;
|
package io.metersphere.notice.service;
|
||||||
|
|
||||||
import io.metersphere.api.dto.APIReportResult;
|
import io.metersphere.api.dto.APIReportResult;
|
||||||
import io.metersphere.base.domain.NoticeDetail;
|
|
||||||
import io.metersphere.base.domain.SystemParameter;
|
import io.metersphere.base.domain.SystemParameter;
|
||||||
import io.metersphere.base.domain.TestCaseWithBLOBs;
|
import io.metersphere.base.domain.TestCaseWithBLOBs;
|
||||||
import io.metersphere.commons.constants.ParamConstants;
|
import io.metersphere.commons.constants.ParamConstants;
|
||||||
|
@ -10,6 +9,7 @@ import io.metersphere.commons.utils.LogUtil;
|
||||||
import io.metersphere.dto.BaseSystemConfigDTO;
|
import io.metersphere.dto.BaseSystemConfigDTO;
|
||||||
import io.metersphere.dto.LoadTestDTO;
|
import io.metersphere.dto.LoadTestDTO;
|
||||||
import io.metersphere.i18n.Translator;
|
import io.metersphere.i18n.Translator;
|
||||||
|
import io.metersphere.notice.domain.NoticeDetail;
|
||||||
import io.metersphere.service.SystemParameterService;
|
import io.metersphere.service.SystemParameterService;
|
||||||
import io.metersphere.service.UserService;
|
import io.metersphere.service.UserService;
|
||||||
import io.metersphere.track.request.testreview.SaveCommentRequest;
|
import io.metersphere.track.request.testreview.SaveCommentRequest;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.metersphere.notice.service;
|
package io.metersphere.notice.service;
|
||||||
|
|
||||||
import io.metersphere.base.domain.Notice;
|
import io.metersphere.base.domain.Notice;
|
||||||
import io.metersphere.base.domain.NoticeDetail;
|
|
||||||
import io.metersphere.base.domain.NoticeExample;
|
import io.metersphere.base.domain.NoticeExample;
|
||||||
import io.metersphere.base.mapper.NoticeMapper;
|
import io.metersphere.base.mapper.NoticeMapper;
|
||||||
import io.metersphere.notice.controller.request.NoticeRequest;
|
import io.metersphere.notice.controller.request.NoticeRequest;
|
||||||
|
import io.metersphere.notice.domain.NoticeDetail;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import io.metersphere.dto.LoadTestDTO;
|
||||||
import io.metersphere.dto.ScheduleDao;
|
import io.metersphere.dto.ScheduleDao;
|
||||||
import io.metersphere.i18n.Translator;
|
import io.metersphere.i18n.Translator;
|
||||||
import io.metersphere.job.sechedule.PerformanceTestJob;
|
import io.metersphere.job.sechedule.PerformanceTestJob;
|
||||||
|
import io.metersphere.notice.domain.NoticeDetail;
|
||||||
import io.metersphere.notice.service.MailService;
|
import io.metersphere.notice.service.MailService;
|
||||||
import io.metersphere.notice.service.NoticeService;
|
import io.metersphere.notice.service.NoticeService;
|
||||||
import io.metersphere.performance.engine.Engine;
|
import io.metersphere.performance.engine.Engine;
|
||||||
|
|
|
@ -61,7 +61,8 @@ public class TestCaseCommentService {
|
||||||
testCaseComments.forEach(testCaseComment -> {
|
testCaseComments.forEach(testCaseComment -> {
|
||||||
String authorId = testCaseComment.getAuthor();
|
String authorId = testCaseComment.getAuthor();
|
||||||
User user = userMapper.selectByPrimaryKey(authorId);
|
User user = userMapper.selectByPrimaryKey(authorId);
|
||||||
testCaseComment.setAuthor(user.getName());
|
String author = user == null ? authorId : user.getName();
|
||||||
|
testCaseComment.setAuthor(author);
|
||||||
});
|
});
|
||||||
return testCaseComments;
|
return testCaseComments;
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,8 @@ public class TestPlanService {
|
||||||
return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlan());
|
return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlan());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int editTestPlan(TestPlan testPlan) {
|
public int editTestPlan(TestPlanDTO testPlan) {
|
||||||
|
editTestPlanProject(testPlan);
|
||||||
testPlan.setUpdateTime(System.currentTimeMillis());
|
testPlan.setUpdateTime(System.currentTimeMillis());
|
||||||
checkTestPlanExist(testPlan);
|
checkTestPlanExist(testPlan);
|
||||||
//进行中状态,写入实际开始时间
|
//进行中状态,写入实际开始时间
|
||||||
|
|
|
@ -90,6 +90,10 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
registerEvents() {
|
registerEvents() {
|
||||||
ApiEvent.$on(LIST_CHANGE, () => {
|
ApiEvent.$on(LIST_CHANGE, () => {
|
||||||
|
// todo 这里偶尔会有 refs 为空的情况
|
||||||
|
if (!this.$refs.projectRecent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$refs.projectRecent.recent();
|
this.$refs.projectRecent.recent();
|
||||||
this.$refs.testRecent.recent();
|
this.$refs.testRecent.recent();
|
||||||
this.$refs.reportRecent.recent();
|
this.$refs.reportRecent.recent();
|
||||||
|
|
|
@ -1,231 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<ms-container v-loading="loading" :element-loading-text="$t('api_report.running')">
|
<ms-api-report-view-detail :report-id="reportId"/>
|
||||||
<ms-main-container>
|
|
||||||
<el-card>
|
|
||||||
<section class="report-container" v-if="this.report.testId">
|
|
||||||
<header class="report-header">
|
|
||||||
<el-row>
|
|
||||||
<el-col>
|
|
||||||
<span>{{ report.projectName }} / </span>
|
|
||||||
<router-link :to="path">{{ report.testName }}</router-link>
|
|
||||||
<span class="time">{{ report.createTime | timestampFormatDate }}</span>
|
|
||||||
<el-button class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)"
|
|
||||||
style="margin-left: 1200px">
|
|
||||||
{{$t('test_track.plan_view.export_report')}}
|
|
||||||
</el-button>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</header>
|
|
||||||
<main v-if="this.isNotRunning">
|
|
||||||
<ms-metric-chart :content="content" :totalTime="totalTime"/>
|
|
||||||
<el-row :gutter="20">
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
|
||||||
<el-tab-pane :label="$t('api_report.total')" name="total">
|
|
||||||
<ms-scenario-results :scenarios="content.scenarios" v-on:requestResult="requestResult"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane name="fail">
|
|
||||||
<template slot="label">
|
|
||||||
<span class="fail">{{ $t('api_report.fail') }}</span>
|
|
||||||
</template>
|
|
||||||
<ms-scenario-results v-on:requestResult="requestResult" :scenarios="fails"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="16" style="margin-top: 40px;">
|
|
||||||
<ms-request-result-tail v-if="isRequestResult" :request="request" :scenario-name="scenarioName"/>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<ms-api-report-export v-if="reportExportVisible" id="apiTestReport" :title="report.testName" :content="content" :total-time="totalTime"/>
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
</el-card>
|
|
||||||
</ms-main-container>
|
|
||||||
</ms-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import MsRequestResult from "./components/RequestResult";
|
import MsApiReportViewDetail from "./ApiReportViewDetail";
|
||||||
import MsRequestResultTail from "./components/RequestResultTail";
|
|
||||||
import MsScenarioResult from "./components/ScenarioResult";
|
|
||||||
import MsMetricChart from "./components/MetricChart";
|
|
||||||
import MsScenarioResults from "./components/ScenarioResults";
|
|
||||||
import MsContainer from "@/business/components/common/components/MsContainer";
|
|
||||||
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
|
|
||||||
import MsApiReportExport from "./ApiReportExport";
|
|
||||||
import {exportPdf} from "../../../../common/js/utils";
|
|
||||||
import html2canvas from "html2canvas";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiReportView",
|
name: "MsApiReportView",
|
||||||
components: {
|
components: {
|
||||||
MsApiReportExport,
|
MsApiReportViewDetail,
|
||||||
MsMainContainer,
|
|
||||||
MsContainer, MsScenarioResults, MsRequestResultTail, MsMetricChart, MsScenarioResult, MsRequestResult
|
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
activeName: "total",
|
|
||||||
content: {},
|
|
||||||
report: {},
|
|
||||||
loading: true,
|
|
||||||
fails: [],
|
|
||||||
totalTime: 0,
|
|
||||||
isRequestResult: false,
|
|
||||||
request: {},
|
|
||||||
scenarioName: null,
|
|
||||||
reportExportVisible: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activated() {
|
|
||||||
this.isRequestResult = false
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init() {
|
|
||||||
this.loading = true;
|
|
||||||
this.report = {};
|
|
||||||
this.content = {};
|
|
||||||
this.fails = [];
|
|
||||||
},
|
|
||||||
handleClick(tab, event) {
|
|
||||||
this.isRequestResult = false
|
|
||||||
},
|
|
||||||
getReport() {
|
|
||||||
this.init();
|
|
||||||
if (this.reportId) {
|
|
||||||
let url = "/api/report/get/" + this.reportId;
|
|
||||||
this.$get(url, response => {
|
|
||||||
this.report = response.data || {};
|
|
||||||
if (response.data) {
|
|
||||||
if (this.isNotRunning) {
|
|
||||||
try {
|
|
||||||
this.content = JSON.parse(this.report.content);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(this.report.content)
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
this.getFails();
|
|
||||||
this.loading = false;
|
|
||||||
} else {
|
|
||||||
setTimeout(this.getReport, 2000)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.loading = false;
|
|
||||||
this.$error(this.$t('api_report.not_exist'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getFails() {
|
|
||||||
if (this.isNotRunning) {
|
|
||||||
this.fails = [];
|
|
||||||
this.totalTime = 0
|
|
||||||
this.content.scenarios.forEach((scenario) => {
|
|
||||||
this.totalTime = this.totalTime + Number(scenario.responseTime)
|
|
||||||
let failScenario = Object.assign({}, scenario);
|
|
||||||
if (scenario.error > 0) {
|
|
||||||
this.fails.push(failScenario);
|
|
||||||
failScenario.requestResults = [];
|
|
||||||
scenario.requestResults.forEach((request) => {
|
|
||||||
if (!request.success) {
|
|
||||||
let failRequest = Object.assign({}, request);
|
|
||||||
failScenario.requestResults.push(failRequest);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
requestResult(requestResult) {
|
|
||||||
this.isRequestResult = false;
|
|
||||||
this.$nextTick(function () {
|
|
||||||
this.isRequestResult = true;
|
|
||||||
this.request = requestResult.request;
|
|
||||||
this.scenarioName = requestResult.scenarioName;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleExport(name) {
|
|
||||||
this.loading = true;
|
|
||||||
this.reportExportVisible = true;
|
|
||||||
let reset = this.exportReportReset;
|
|
||||||
|
|
||||||
this.$nextTick(function () {
|
|
||||||
setTimeout(() => {
|
|
||||||
html2canvas(document.getElementById('apiTestReport'), {
|
|
||||||
scale: 2
|
|
||||||
}).then(function(canvas) {
|
|
||||||
exportPdf(name, [canvas]);
|
|
||||||
reset();
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
exportReportReset() {
|
|
||||||
this.reportExportVisible = false;
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
'$route': 'getReport',
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.getReport();
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
reportId: function () {
|
reportId: function () {
|
||||||
return this.$route.params.reportId;
|
return this.$route.params.reportId;
|
||||||
},
|
},
|
||||||
path() {
|
|
||||||
return "/api/test/edit?id=" + this.report.testId;
|
|
||||||
},
|
|
||||||
isNotRunning() {
|
|
||||||
return "Running" !== this.report.status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.report-container .el-tabs__header {
|
|
||||||
margin-bottom: 1px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.report-container {
|
|
||||||
height: calc(100vh - 155px);
|
|
||||||
min-height: 600px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-header {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-header a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-header .time {
|
|
||||||
color: #909399;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-container .fail {
|
|
||||||
color: #F56C6C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-container .is-active .fail {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.export-button {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
<template>
|
||||||
|
<ms-container v-loading="loading" :element-loading-text="$t('api_report.running')">
|
||||||
|
<ms-main-container>
|
||||||
|
<el-card>
|
||||||
|
<section class="report-container" v-if="this.report.testId">
|
||||||
|
|
||||||
|
<ms-api-report-view-header :report="report" @reportExport="handleExport"/>
|
||||||
|
|
||||||
|
<main v-if="this.isNotRunning">
|
||||||
|
<ms-metric-chart :content="content" :totalTime="totalTime"/>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||||
|
<el-tab-pane :label="$t('api_report.total')" name="total">
|
||||||
|
<ms-scenario-results :scenarios="content.scenarios" v-on:requestResult="requestResult"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane name="fail">
|
||||||
|
<template slot="label">
|
||||||
|
<span class="fail">{{ $t('api_report.fail') }}</span>
|
||||||
|
</template>
|
||||||
|
<ms-scenario-results v-on:requestResult="requestResult" :scenarios="fails"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="16" style="margin-top: 40px;">
|
||||||
|
<ms-request-result-tail v-if="isRequestResult" :request="request" :scenario-name="scenarioName"/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<ms-api-report-export v-if="reportExportVisible" id="apiTestReport" :title="report.testName" :content="content" :total-time="totalTime"/>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
</el-card>
|
||||||
|
</ms-main-container>
|
||||||
|
</ms-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import MsRequestResult from "./components/RequestResult";
|
||||||
|
import MsRequestResultTail from "./components/RequestResultTail";
|
||||||
|
import MsScenarioResult from "./components/ScenarioResult";
|
||||||
|
import MsMetricChart from "./components/MetricChart";
|
||||||
|
import MsScenarioResults from "./components/ScenarioResults";
|
||||||
|
import MsContainer from "@/business/components/common/components/MsContainer";
|
||||||
|
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
|
||||||
|
import MsApiReportExport from "./ApiReportExport";
|
||||||
|
import {exportPdf} from "../../../../common/js/utils";
|
||||||
|
import html2canvas from "html2canvas";
|
||||||
|
import MsApiReportViewHeader from "./ApiReportViewHeader";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MsApiReportViewDetail",
|
||||||
|
components: {
|
||||||
|
MsApiReportViewHeader,
|
||||||
|
MsApiReportExport,
|
||||||
|
MsMainContainer,
|
||||||
|
MsContainer, MsScenarioResults, MsRequestResultTail, MsMetricChart, MsScenarioResult, MsRequestResult
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeName: "total",
|
||||||
|
content: {},
|
||||||
|
report: {},
|
||||||
|
loading: true,
|
||||||
|
fails: [],
|
||||||
|
totalTime: 0,
|
||||||
|
isRequestResult: false,
|
||||||
|
request: {},
|
||||||
|
scenarioName: null,
|
||||||
|
reportExportVisible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: ['reportId'],
|
||||||
|
activated() {
|
||||||
|
this.isRequestResult = false;
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
reportId() {
|
||||||
|
this.getReport();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init() {
|
||||||
|
this.loading = true;
|
||||||
|
this.report = {};
|
||||||
|
this.content = {};
|
||||||
|
this.fails = [];
|
||||||
|
this.report = {};
|
||||||
|
this.isRequestResult = false;
|
||||||
|
},
|
||||||
|
handleClick(tab, event) {
|
||||||
|
this.isRequestResult = false
|
||||||
|
},
|
||||||
|
getReport() {
|
||||||
|
this.init();
|
||||||
|
if (this.reportId) {
|
||||||
|
let url = "/api/report/get/" + this.reportId;
|
||||||
|
this.$get(url, response => {
|
||||||
|
this.report = response.data || {};
|
||||||
|
if (response.data) {
|
||||||
|
if (this.isNotRunning) {
|
||||||
|
try {
|
||||||
|
this.content = JSON.parse(this.report.content);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(this.report.content)
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
this.getFails();
|
||||||
|
this.loading = false;
|
||||||
|
} else {
|
||||||
|
setTimeout(this.getReport, 2000)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.loading = false;
|
||||||
|
this.$error(this.$t('api_report.not_exist'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFails() {
|
||||||
|
if (this.isNotRunning) {
|
||||||
|
this.fails = [];
|
||||||
|
this.totalTime = 0
|
||||||
|
this.content.scenarios.forEach((scenario) => {
|
||||||
|
this.totalTime = this.totalTime + Number(scenario.responseTime)
|
||||||
|
let failScenario = Object.assign({}, scenario);
|
||||||
|
if (scenario.error > 0) {
|
||||||
|
this.fails.push(failScenario);
|
||||||
|
failScenario.requestResults = [];
|
||||||
|
scenario.requestResults.forEach((request) => {
|
||||||
|
if (!request.success) {
|
||||||
|
let failRequest = Object.assign({}, request);
|
||||||
|
failScenario.requestResults.push(failRequest);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
requestResult(requestResult) {
|
||||||
|
this.isRequestResult = false;
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.isRequestResult = true;
|
||||||
|
this.request = requestResult.request;
|
||||||
|
this.scenarioName = requestResult.scenarioName;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport(name) {
|
||||||
|
this.loading = true;
|
||||||
|
this.reportExportVisible = true;
|
||||||
|
let reset = this.exportReportReset;
|
||||||
|
|
||||||
|
this.$nextTick(function () {
|
||||||
|
setTimeout(() => {
|
||||||
|
html2canvas(document.getElementById('apiTestReport'), {
|
||||||
|
scale: 2
|
||||||
|
}).then(function(canvas) {
|
||||||
|
exportPdf(name, [canvas]);
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
exportReportReset() {
|
||||||
|
this.reportExportVisible = false;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.getReport();
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
path() {
|
||||||
|
return "/api/test/edit?id=" + this.report.testId;
|
||||||
|
},
|
||||||
|
isNotRunning() {
|
||||||
|
return "Running" !== this.report.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.report-container .el-tabs__header {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.report-container {
|
||||||
|
height: calc(100vh - 155px);
|
||||||
|
min-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header .time {
|
||||||
|
color: #909399;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-container .fail {
|
||||||
|
color: #F56C6C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-container .is-active .fail {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<template>
|
||||||
|
<header class="report-header">
|
||||||
|
<el-row>
|
||||||
|
<el-col>
|
||||||
|
<span>{{ report.projectName }} / </span>
|
||||||
|
<router-link :to="path">{{ report.testName }}</router-link>
|
||||||
|
<span class="time">{{ report.createTime | timestampFormatDate }}</span>
|
||||||
|
<el-button :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)"
|
||||||
|
style="margin-left: 1200px">
|
||||||
|
{{$t('test_track.plan_view.export_report')}}
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MsApiReportViewHeader",
|
||||||
|
props: ['report'],
|
||||||
|
computed: {
|
||||||
|
path() {
|
||||||
|
return "/api/test/edit?id=" + this.report.testId;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isReadOnly: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!checkoutTestManagerOrTestUser()) {
|
||||||
|
this.isReadOnly = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleExport(name) {
|
||||||
|
this.$emit('reportExport', name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.export-button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -2,10 +2,13 @@
|
||||||
<div class="request-result">
|
<div class="request-result">
|
||||||
<div @click="active">
|
<div @click="active">
|
||||||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||||
<el-col :span="16">
|
<el-col :span="12">
|
||||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
||||||
{{scenarioName}}
|
{{scenarioName}}
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
{{$t('api_report.start_time')}}
|
||||||
|
</el-col>
|
||||||
<el-col :span="2">
|
<el-col :span="2">
|
||||||
{{$t('api_report.response_time')}}
|
{{$t('api_report.response_time')}}
|
||||||
</el-col>
|
</el-col>
|
||||||
|
@ -20,17 +23,20 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||||
<el-col :span="4">
|
<el-col :span="2">
|
||||||
<div class="method">
|
<div class="method">
|
||||||
{{request.method}}
|
{{request.method}}
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="10">
|
||||||
<div class="name">{{request.name}}</div>
|
<div class="name">{{request.name}}</div>
|
||||||
<el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800">
|
<el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800">
|
||||||
<div class="url">{{request.url}}</div>
|
<div class="url">{{request.url}}</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
{{request.startTime | timestampFormatDate(true) }}
|
||||||
|
</el-col>
|
||||||
<el-col :span="2">
|
<el-col :span="2">
|
||||||
<div class="time">
|
<div class="time">
|
||||||
{{request.responseResult.responseTime}}
|
{{request.responseResult.responseTime}}
|
||||||
|
|
|
@ -14,9 +14,13 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
||||||
<el-button type="primary" plain :disabled="isReadOnly" @click="saveTest">
|
<el-tooltip :content="'Ctrl + S'"
|
||||||
{{ $t('commons.save') }}
|
placement="top"
|
||||||
</el-button>
|
:enterable="false">
|
||||||
|
<el-button type="primary" plain :disabled="isReadOnly" @click="saveTest">
|
||||||
|
{{ $t('commons.save') }}
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
<el-button type="primary" plain :disabled="isReadOnly"
|
<el-button type="primary" plain :disabled="isReadOnly"
|
||||||
@click="saveRunTest">
|
@click="saveRunTest">
|
||||||
|
@ -67,7 +71,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
|
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
|
||||||
import {Test, Scenario} from "./model/ScenarioModel"
|
import {Scenario, Test} from "./model/ScenarioModel"
|
||||||
import MsApiReportStatus from "../report/ApiReportStatus";
|
import MsApiReportStatus from "../report/ApiReportStatus";
|
||||||
import MsApiReportDialog from "./ApiReportDialog";
|
import MsApiReportDialog from "./ApiReportDialog";
|
||||||
import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils";
|
import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils";
|
||||||
|
@ -368,11 +372,25 @@ export default {
|
||||||
this.debugReportId = response.data;
|
this.debugReportId = response.data;
|
||||||
this.resetBodyFile();
|
this.resetBodyFile();
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
handleEvent(event) {
|
||||||
|
if (event.keyCode === 83 && event.ctrlKey) {
|
||||||
|
console.log('拦截到 ctrl + s');//ctrl+s
|
||||||
|
this.saveTest();
|
||||||
|
event.preventDefault();
|
||||||
|
event.returnValue = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.init();
|
this.init();
|
||||||
|
//
|
||||||
|
document.addEventListener('keydown', this.handleEvent)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
document.removeEventListener('keydown', this.handleEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<div class="kv-row" v-for="(item, index) in items" :key="index">
|
<div class="kv-row" v-for="(item, index) in items" :key="index">
|
||||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||||
<el-col v-if="isShowEnable" class="kv-checkbox">
|
<el-col v-if="isShowEnable" class="kv-checkbox">
|
||||||
<input type="checkbox" v-if="!isDisable(index)" @change="change" :value="item.uuid" v-model="checkedValues"
|
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
|
||||||
:disabled="isDisable(index) || isReadOnly"/>
|
:disabled="isReadOnly"/>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col>
|
<el-col>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
checkedValues: []
|
// checkedValues: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -79,10 +79,6 @@
|
||||||
let isNeedCreate = true;
|
let isNeedCreate = true;
|
||||||
let removeIndex = -1;
|
let removeIndex = -1;
|
||||||
this.items.forEach((item, index) => {
|
this.items.forEach((item, index) => {
|
||||||
// 启用行赋值
|
|
||||||
if (this.isShowEnable) {
|
|
||||||
item.enable = this.checkedValues.indexOf(item.uuid) != -1 ? true : false;
|
|
||||||
}
|
|
||||||
if (!item.name && !item.value) {
|
if (!item.name && !item.value) {
|
||||||
// 多余的空行
|
// 多余的空行
|
||||||
if (index !== this.items.length - 1) {
|
if (index !== this.items.length - 1) {
|
||||||
|
@ -93,13 +89,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (isNeedCreate) {
|
if (isNeedCreate) {
|
||||||
// 往后台送入的复选框值布尔值
|
this.items.push(new KeyValue({enable: true}));
|
||||||
if (this.isShowEnable) {
|
|
||||||
this.items[this.items.length - 1].enable = true;
|
|
||||||
// v-model 选中状态
|
|
||||||
this.checkedValues.push(this.items[this.items.length - 1].uuid);
|
|
||||||
}
|
|
||||||
this.items.push(new KeyValue());
|
|
||||||
}
|
}
|
||||||
this.$emit('change', this.items);
|
this.$emit('change', this.items);
|
||||||
// TODO 检查key重复
|
// TODO 检查key重复
|
||||||
|
@ -112,9 +102,6 @@
|
||||||
let results = queryString ? suggestions.filter(this.createFilter(queryString)) : suggestions;
|
let results = queryString ? suggestions.filter(this.createFilter(queryString)) : suggestions;
|
||||||
cb(results);
|
cb(results);
|
||||||
},
|
},
|
||||||
uuid: function () {
|
|
||||||
return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1);
|
|
||||||
},
|
|
||||||
createFilter(queryString) {
|
createFilter(queryString) {
|
||||||
return (restaurant) => {
|
return (restaurant) => {
|
||||||
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
|
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
|
||||||
|
@ -123,15 +110,7 @@
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.items.length === 0) {
|
if (this.items.length === 0) {
|
||||||
this.items.push(new KeyValue());
|
this.items.push(new KeyValue({enable: true}));
|
||||||
} else if (this.isShowEnable) {
|
|
||||||
this.items.forEach((item, index) => {
|
|
||||||
let uuid = this.uuid();
|
|
||||||
item.uuid = uuid;
|
|
||||||
if (item.enable) {
|
|
||||||
this.checkedValues.push(uuid);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,27 @@
|
||||||
<span class="kv-description" v-if="description">
|
<span class="kv-description" v-if="description">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
||||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||||
|
|
||||||
<el-col class="kv-checkbox">
|
<el-col class="kv-checkbox">
|
||||||
<input type="checkbox" v-if="!isDisable(index)" @change="change" :value="item.uuid" v-model="checkedValues"
|
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
|
||||||
:disabled="isDisable(index) || isReadOnly"/>
|
:disabled="isReadOnly"/>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col>
|
<el-col>
|
||||||
|
|
||||||
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
|
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
|
||||||
@change="change" :placeholder="keyText" show-word-limit>
|
@change="change" :placeholder="keyText" show-word-limit>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type">
|
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type" @change="typeChange(item)">
|
||||||
<el-option value="text"/>
|
<el-option value="text"/>
|
||||||
<el-option value="file"/>
|
<el-option value="file"/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
||||||
<el-autocomplete :disabled="isReadOnly" :maxlength="200" v-if="suggestions" v-model="item.name" size="small"
|
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||||
|
|
||||||
</el-col>
|
</el-col>
|
||||||
|
@ -44,6 +45,13 @@
|
||||||
<el-col v-if="item.type === 'file'">
|
<el-col v-if="item.type === 'file'">
|
||||||
<ms-api-body-file-upload :parameter="item"/>
|
<ms-api-body-file-upload :parameter="item"/>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
|
<el-col v-if="type === 'body'">
|
||||||
|
<el-input :disabled="isReadOnly" v-model="item.contentType" size="small" maxlength="100"
|
||||||
|
@change="change" :placeholder="$t('api_test.request.content_type')" show-word-limit>
|
||||||
|
</el-input>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
<el-col class="kv-delete">
|
<el-col class="kv-delete">
|
||||||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||||
:disabled="isDisable(index) || isReadOnly"/>
|
:disabled="isDisable(index) || isReadOnly"/>
|
||||||
|
@ -85,7 +93,6 @@
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentItem: null,
|
currentItem: null,
|
||||||
checkedValues: []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -98,9 +105,6 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
remove: function (index) {
|
remove: function (index) {
|
||||||
// 移除勾选内容
|
|
||||||
let checkIndex = this.checkedValues.indexOf(this.parameters[index].uuid);
|
|
||||||
checkIndex != -1 ? this.checkedValues.splice(checkIndex, 1) : this.checkedValues;
|
|
||||||
// 移除整行输入控件及内容
|
// 移除整行输入控件及内容
|
||||||
this.parameters.splice(index, 1);
|
this.parameters.splice(index, 1);
|
||||||
this.$emit('change', this.parameters);
|
this.$emit('change', this.parameters);
|
||||||
|
@ -109,9 +113,6 @@
|
||||||
let isNeedCreate = true;
|
let isNeedCreate = true;
|
||||||
let removeIndex = -1;
|
let removeIndex = -1;
|
||||||
this.parameters.forEach((item, index) => {
|
this.parameters.forEach((item, index) => {
|
||||||
// 启用行赋值
|
|
||||||
item.enable = this.checkedValues.indexOf(item.uuid) != -1 ? true : false;
|
|
||||||
|
|
||||||
if (!item.name && !item.value) {
|
if (!item.name && !item.value) {
|
||||||
// 多余的空行
|
// 多余的空行
|
||||||
if (index !== this.parameters.length - 1) {
|
if (index !== this.parameters.length - 1) {
|
||||||
|
@ -122,11 +123,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (isNeedCreate) {
|
if (isNeedCreate) {
|
||||||
// 往后台送入的复选框值布尔值
|
this.parameters.push(new KeyValue({type: 'text', enable: true, uuid: this.uuid(), contentType: 'text/plain'}));
|
||||||
this.parameters[this.parameters.length - 1].enable = true;
|
|
||||||
// v-model 选中状态
|
|
||||||
this.checkedValues.push(this.parameters[this.parameters.length - 1].uuid);
|
|
||||||
this.parameters.push(new KeyValue(null, null, 'text', false, this.uuid()));
|
|
||||||
}
|
}
|
||||||
this.$emit('change', this.parameters);
|
this.$emit('change', this.parameters);
|
||||||
// TODO 检查key重复
|
// TODO 检查key重复
|
||||||
|
@ -161,22 +158,18 @@
|
||||||
advanced(item) {
|
advanced(item) {
|
||||||
this.$refs.variableAdvance.open();
|
this.$refs.variableAdvance.open();
|
||||||
this.currentItem = item;
|
this.currentItem = item;
|
||||||
this.itemValue = '';
|
|
||||||
this.mockVariableFuncs = [];
|
|
||||||
},
|
},
|
||||||
|
typeChange(item) {
|
||||||
|
if (item.type === 'file') {
|
||||||
|
item.contentType = 'application/octet-stream';
|
||||||
|
} else {
|
||||||
|
item.contentType = 'text/plain';
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.parameters.length === 0) {
|
if (this.parameters.length === 0) {
|
||||||
this.parameters.push(new KeyValue(null, null, 'text', false, this.uuid()));
|
this.parameters.push(new KeyValue( {type: 'text', enable: true, uuid: this.uuid(), contentType: 'text/plain'}));
|
||||||
} else {
|
|
||||||
this.parameters.forEach((item, index) => {
|
|
||||||
let uuid = this.uuid();
|
|
||||||
item.uuid = uuid;
|
|
||||||
if (item.enable) {
|
|
||||||
this.checkedValues.push(uuid);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,7 +173,7 @@ export default {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
if (this.environment) {
|
if (this.environment) {
|
||||||
let variables = JSON.parse(this.environment.variables);
|
let variables = this.environment.config.commonConfig.variables;
|
||||||
this.environmentParams = [
|
this.environmentParams = [
|
||||||
{
|
{
|
||||||
name: this.environment.name,
|
name: this.environment.name,
|
||||||
|
|
|
@ -105,9 +105,9 @@
|
||||||
|
|
||||||
.api-body-upload {
|
.api-body-upload {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
/*border: 1px solid #EBEEF5;*/
|
border: 1px solid #EBEEF5;
|
||||||
/*padding: 2px;*/
|
padding: 2px;
|
||||||
/*border-radius: 4px;*/
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-item {
|
.upload-item {
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
codeTemplates: [
|
codeTemplates: [
|
||||||
{
|
{
|
||||||
title: this.$t('api_test.request.processor.code_template_get_variable'),
|
title: this.$t('api_test.request.processor.code_template_get_variable'),
|
||||||
value: 'vars.get("variable_name");',
|
value: 'vars.get("variable_name")',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: this.$t('api_test.request.processor.code_template_set_variable'),
|
title: this.$t('api_test.request.processor.code_template_set_variable'),
|
||||||
|
@ -42,17 +42,17 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: this.$t('api_test.request.processor.code_template_get_response_header'),
|
title: this.$t('api_test.request.processor.code_template_get_response_header'),
|
||||||
value: 'prev.getResponseHeaders();',
|
value: 'prev.getResponseHeaders()',
|
||||||
disabled: this.isPreProcessor
|
disabled: this.isPreProcessor
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: this.$t('api_test.request.processor.code_template_get_response_code'),
|
title: this.$t('api_test.request.processor.code_template_get_response_code'),
|
||||||
value: 'prev.getResponseCode();',
|
value: 'prev.getResponseCode()',
|
||||||
disabled: this.isPreProcessor
|
disabled: this.isPreProcessor
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: this.$t('api_test.request.processor.code_template_get_response_result'),
|
title: this.$t('api_test.request.processor.code_template_get_response_result'),
|
||||||
value: 'prev.getResponseDataAsString();',
|
value: 'prev.getResponseDataAsString()',
|
||||||
disabled: this.isPreProcessor
|
disabled: this.isPreProcessor
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -93,6 +93,9 @@
|
||||||
this.jsr223Processor.script = "";
|
this.jsr223Processor.script = "";
|
||||||
}
|
}
|
||||||
this.jsr223Processor.script += template.value;
|
this.jsr223Processor.script += template.value;
|
||||||
|
if (this.jsr223Processor.language === 'beanshell') {
|
||||||
|
this.jsr223Processor.script += ';';
|
||||||
|
}
|
||||||
this.reload();
|
this.reload();
|
||||||
},
|
},
|
||||||
reload() {
|
reload() {
|
||||||
|
|
|
@ -156,7 +156,7 @@ export default {
|
||||||
let url = new URL(urlStr);
|
let url = new URL(urlStr);
|
||||||
url.searchParams.forEach((value, key) => {
|
url.searchParams.forEach((value, key) => {
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
this.request.parameters.splice(0, 0, new KeyValue(key, value));
|
this.request.parameters.splice(0, 0, new KeyValue({name: name, value: value}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return url;
|
return url;
|
||||||
|
|
|
@ -5,11 +5,24 @@
|
||||||
<el-input v-model="request.name" maxlength="300" show-word-limit/>
|
<el-input v-model="request.name" maxlength="300" show-word-limit/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('api_test.request.sql.dataSource')" prop="dataSource">
|
<div class="one-row">
|
||||||
<el-select v-model="request.dataSource">
|
<el-form-item :label="$t('api_test.request.sql.dataSource')" prop="dataSource">
|
||||||
<el-option v-for="(item, index) in databaseConfigsOptions" :key="index" :value="item.id" :label="item.name"/>
|
<el-select v-model="request.dataSource">
|
||||||
</el-select>
|
<el-option v-for="(item, index) in databaseConfigsOptions" :key="index" :value="item.id" :label="item.name"/>
|
||||||
</el-form-item>
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('api_test.request.sql.timeout')" prop="queryTimeout">
|
||||||
|
<el-input-number :disabled="isReadOnly" size="mini" v-model="request.queryTimeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-switch
|
||||||
|
v-model="request.useEnvironment"
|
||||||
|
:active-text="$t('api_test.request.refer_to_environment')" @change="getDatabaseConfigsOptions">
|
||||||
|
</el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-form-item :label="$t('api_test.request.sql.result_variable')" prop="resultVariable">
|
<el-form-item :label="$t('api_test.request.sql.result_variable')" prop="resultVariable">
|
||||||
<el-input v-model="request.resultVariable" maxlength="300" show-word-limit/>
|
<el-input v-model="request.resultVariable" maxlength="300" show-word-limit/>
|
||||||
|
@ -25,17 +38,6 @@
|
||||||
<!--</el-select>-->
|
<!--</el-select>-->
|
||||||
<!--</el-form-item>-->
|
<!--</el-form-item>-->
|
||||||
|
|
||||||
<el-form-item :label="$t('api_test.request.sql.timeout')" prop="queryTimeout">
|
|
||||||
<el-input-number :disabled="isReadOnly" size="mini" v-model="request.queryTimeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item>
|
|
||||||
<el-switch
|
|
||||||
v-model="request.useEnvironment"
|
|
||||||
:active-text="$t('api_test.request.refer_to_environment')" @change="getDatabaseConfigsOptions">
|
|
||||||
</el-switch>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small" type="primary" @click="runDebug">{{$t('api_test.request.debug')}}</el-button>
|
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small" type="primary" @click="runDebug">{{$t('api_test.request.debug')}}</el-button>
|
||||||
|
|
||||||
<el-tabs v-model="activeName">
|
<el-tabs v-model="activeName">
|
||||||
|
@ -147,4 +149,12 @@
|
||||||
height: calc(100vh - 570px);
|
height: calc(100vh - 570px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.one-row .el-form-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.one-row .el-form-item:nth-child(2) {
|
||||||
|
margin-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -344,6 +344,9 @@ export class HTTPSamplerArguments extends Element {
|
||||||
}
|
}
|
||||||
elementProp.stringProp('Argument.value', arg.value);
|
elementProp.stringProp('Argument.value', arg.value);
|
||||||
elementProp.stringProp('Argument.metadata', arg.metadata || "=");
|
elementProp.stringProp('Argument.metadata', arg.metadata || "=");
|
||||||
|
if (arg.contentType) {
|
||||||
|
elementProp.stringProp('HTTPArgument.content_type', arg.contentType, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -363,7 +366,7 @@ export class HTTPsamplerFiles extends Element {
|
||||||
let elementProp = collectionProp.elementProp(arg.value, 'HTTPFileArg');
|
let elementProp = collectionProp.elementProp(arg.value, 'HTTPFileArg');
|
||||||
elementProp.stringProp('File.path', arg.value);
|
elementProp.stringProp('File.path', arg.value);
|
||||||
elementProp.stringProp('File.paramname', arg.name);
|
elementProp.stringProp('File.paramname', arg.name);
|
||||||
elementProp.stringProp('File.mimetype', arg.metadata || "application/octet-stream");
|
elementProp.stringProp('File.mimetype', arg.contentType || "application/octet-stream");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -656,35 +656,18 @@ export class Body extends BaseConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KeyValue extends BaseConfig {
|
export class KeyValue extends BaseConfig {
|
||||||
constructor() {
|
constructor(options) {
|
||||||
let options, key, value, type, enable, uuid;
|
options = options || {};
|
||||||
if (arguments.length === 1) {
|
options.enable = options.enable != false ? true : false;
|
||||||
options = arguments[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
key = arguments[0];
|
|
||||||
value = arguments[1];
|
|
||||||
}
|
|
||||||
if (arguments.length === 3) {
|
|
||||||
key = arguments[0];
|
|
||||||
value = arguments[1];
|
|
||||||
type = arguments[2];
|
|
||||||
}
|
|
||||||
if (arguments.length === 5) {
|
|
||||||
key = arguments[0];
|
|
||||||
value = arguments[1];
|
|
||||||
type = arguments[2];
|
|
||||||
enable = arguments[3];
|
|
||||||
uuid = arguments[4];
|
|
||||||
}
|
|
||||||
super();
|
super();
|
||||||
this.name = key;
|
this.name = undefined;
|
||||||
this.value = value;
|
this.value = undefined;
|
||||||
this.type = type;
|
this.type = undefined;
|
||||||
this.files = undefined;
|
this.files = undefined;
|
||||||
this.enable = enable;
|
this.enable = undefined;
|
||||||
this.uuid = uuid;
|
this.uuid = undefined;
|
||||||
|
this.contentType = undefined;
|
||||||
this.set(options);
|
this.set(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -956,7 +939,7 @@ class JMXHttpRequest {
|
||||||
this.domain = environment.config.httpConfig.domain;
|
this.domain = environment.config.httpConfig.domain;
|
||||||
this.port = environment.config.httpConfig.port;
|
this.port = environment.config.httpConfig.port;
|
||||||
this.protocol = environment.config.httpConfig.protocol;
|
this.protocol = environment.config.httpConfig.protocol;
|
||||||
let url = new URL(environment.config.httpConfig.protocol + "://" + environment.config.commonConfig.socket);
|
let url = new URL(environment.config.httpConfig.protocol + "://" + environment.config.httpConfig.socket);
|
||||||
this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname + (request.path ? request.path : '')));
|
this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname + (request.path ? request.path : '')));
|
||||||
}
|
}
|
||||||
this.connectTimeout = request.connectTimeout;
|
this.connectTimeout = request.connectTimeout;
|
||||||
|
@ -1117,7 +1100,7 @@ class JMXGenerator {
|
||||||
}
|
}
|
||||||
envArray.forEach(item => {
|
envArray.forEach(item => {
|
||||||
if (item.name && !keys.has(item.name)) {
|
if (item.name && !keys.has(item.name)) {
|
||||||
target.push(new KeyValue(item.name, item.value));
|
target.push(new KeyValue({name: item.name, value: item.value}));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1294,7 +1277,7 @@ class JMXGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.headers.push(new KeyValue('Content-Type', type));
|
request.headers.push(new KeyValue({name: 'Content-Type', value: type}));
|
||||||
}
|
}
|
||||||
|
|
||||||
addRequestArguments(httpSamplerProxy, request) {
|
addRequestArguments(httpSamplerProxy, request) {
|
||||||
|
@ -1323,7 +1306,7 @@ class JMXGenerator {
|
||||||
let files = [];
|
let files = [];
|
||||||
let kvs = this.filterKVFile(request.body.kvs);
|
let kvs = this.filterKVFile(request.body.kvs);
|
||||||
kvs.forEach(kv => {
|
kvs.forEach(kv => {
|
||||||
if (kv.files) {
|
if ((kv.enable != false) && kv.files) {
|
||||||
kv.files.forEach(file => {
|
kv.files.forEach(file => {
|
||||||
let arg = {};
|
let arg = {};
|
||||||
arg.name = kv.name;
|
arg.name = kv.name;
|
||||||
|
|
|
@ -63,10 +63,10 @@
|
||||||
>
|
>
|
||||||
<template v-slot:default="{row}">
|
<template v-slot:default="{row}">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="row.enable"
|
v-model="row.enable"
|
||||||
active-value="true"
|
active-value="true"
|
||||||
inactive-value="false"
|
inactive-value="false"
|
||||||
inactive-color="#ff4949"
|
inactive-color="#DCDFE6"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
|
@ -98,6 +98,10 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
registerEvents() {
|
registerEvents() {
|
||||||
PerformanceEvent.$on(LIST_CHANGE, () => {
|
PerformanceEvent.$on(LIST_CHANGE, () => {
|
||||||
|
// todo 这里偶尔会有 refs 为空的情况
|
||||||
|
if (!this.$refs.projectRecent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$refs.projectRecent.recent();
|
this.$refs.projectRecent.recent();
|
||||||
this.$refs.testRecent.recent();
|
this.$refs.testRecent.recent();
|
||||||
this.$refs.reportRecent.recent();
|
this.$refs.reportRecent.recent();
|
||||||
|
|
|
@ -150,6 +150,10 @@ export default {
|
||||||
},
|
},
|
||||||
registerEvents() {
|
registerEvents() {
|
||||||
TrackEvent.$on(LIST_CHANGE, () => {
|
TrackEvent.$on(LIST_CHANGE, () => {
|
||||||
|
// todo 这里偶尔会有 refs 为空的情况
|
||||||
|
if (!this.$refs.projectRecent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$refs.projectRecent.recent();
|
this.$refs.projectRecent.recent();
|
||||||
this.$refs.planRecent.recent();
|
this.$refs.planRecent.recent();
|
||||||
this.$refs.caseRecent.recent();
|
this.$refs.caseRecent.recent();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ms-report-export-template :title="title" :type="$t('report.load_test_report')">
|
<ms-report-export-template :title="title" :type="$t('report.test_plan_report')">
|
||||||
<div v-for="(item, index) in previews" :key="item.id">
|
<div v-for="(item, index) in previews" :key="item.id">
|
||||||
<template-component :isReportView="true" :metric="metric" :preview="item" :index="index" ref="templateComponent"/>
|
<template-component :isReportView="true" :metric="metric" :preview="item" :index="index" ref="templateComponent"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -449,6 +449,7 @@
|
||||||
testRun(reportId) {
|
testRun(reportId) {
|
||||||
this.testCase.reportId = reportId;
|
this.testCase.reportId = reportId;
|
||||||
this.saveReport(reportId);
|
this.saveReport(reportId);
|
||||||
|
this.activeTab = 'result';
|
||||||
},
|
},
|
||||||
testTabChange(data) {
|
testTabChange(data) {
|
||||||
if (this.testCase.type == 'performance' && data.paneName == 'result') {
|
if (this.testCase.type == 'performance' && data.paneName == 'result') {
|
||||||
|
|
|
@ -36,12 +36,12 @@
|
||||||
<div v-for="(item, index) in previews" :key="item.id">
|
<div v-for="(item, index) in previews" :key="item.id">
|
||||||
<template-component :isReportView="true" :metric="metric" :preview="item" :index="index" ref="templateComponent"/>
|
<template-component :isReportView="true" :metric="metric" :preview="item" :index="index" ref="templateComponent"/>
|
||||||
</div>
|
</div>
|
||||||
<ms-test-case-report-export v-if="reportExportVisible" id="testCaseReportExport" :title="report.name" :metric="metric" :previews="previews"/>
|
|
||||||
</el-main>
|
</el-main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
<ms-test-case-report-export v-if="reportExportVisible" id="testCaseReportExport" :title="report.name" :metric="metric" :previews="previews"/>
|
||||||
<test-case-report-template-edit :metric="metric" ref="templateEdit" @refresh="getReport"/>
|
<test-case-report-template-edit :metric="metric" ref="templateEdit" @refresh="getReport"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-header>
|
</el-header>
|
||||||
|
|
||||||
<ms-api-scenario-config :project-id="test.projectId" :is-read-only="true" :scenarios="test.scenarioDefinition" ref="config"/>
|
<ms-api-scenario-config :test="test" :project-id="test.projectId" :is-read-only="true" :scenarios="test.scenarioDefinition" ref="config"/>
|
||||||
|
|
||||||
</el-container>
|
</el-container>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
|
@ -1,136 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<ms-container>
|
<div>
|
||||||
<ms-main-container>
|
<span v-if="!reportId">{{$t('commons.not_performed_yet')}}</span>
|
||||||
<span v-if="!reportId">{{$t('commons.not_performed_yet')}}</span>
|
<ms-api-report-view-detail v-if="reportId" :report-id="reportId"/>
|
||||||
<el-card v-if="reportId">
|
</div>
|
||||||
<section class="report-container" v-loading="loading">
|
|
||||||
<header class="report-header">
|
|
||||||
<span>{{report.projectName}} / </span>
|
|
||||||
<span class="time">{{report.createTime | timestampFormatDate}}</span>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<ms-metric-chart v-if="content" :content="content"/>
|
|
||||||
<el-tabs v-model="activeName">
|
|
||||||
<el-tab-pane :label="$t('api_report.total')" name="total">
|
|
||||||
<ms-scenario-results :scenarios="content.scenarios"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane name="fail">
|
|
||||||
<template slot="label">
|
|
||||||
<span class="fail">{{$t('api_report.fail')}}</span>
|
|
||||||
</template>
|
|
||||||
<ms-scenario-results :scenarios="fails"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
</el-card>
|
|
||||||
</ms-main-container>
|
|
||||||
</ms-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import MsScenarioResult from "../../../../../api/report/components/ScenarioResult";
|
import MsApiReportViewDetail from "../../../../../api/report/ApiReportViewDetail";
|
||||||
import MsMetricChart from "../../../../../api/report/components/MetricChart";
|
|
||||||
import MsScenarioResults from "../../../../../api/report/components/ScenarioResults";
|
|
||||||
import MsRequestResult from "../../../../../api/report/components/RequestResult";
|
|
||||||
import MsContainer from "../../../../../common/components/MsContainer";
|
|
||||||
import MsMainContainer from "../../../../../common/components/MsMainContainer";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ApiTestResult",
|
name: "ApiTestResult",
|
||||||
components: {MsMainContainer, MsContainer, MsRequestResult, MsScenarioResults, MsMetricChart, MsScenarioResult},
|
components: {MsApiReportViewDetail},
|
||||||
data() {
|
props: ['reportId'],
|
||||||
return {
|
}
|
||||||
activeName: "total",
|
|
||||||
content: {},
|
|
||||||
report: {},
|
|
||||||
loading: true,
|
|
||||||
fails: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props:['reportId'],
|
|
||||||
watch: {
|
|
||||||
reportId() {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.init();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init() {
|
|
||||||
this.loading = true;
|
|
||||||
this.report = {};
|
|
||||||
this.content = {};
|
|
||||||
this.fails = [];
|
|
||||||
this.getReport();
|
|
||||||
},
|
|
||||||
getReport() {
|
|
||||||
if (this.reportId) {
|
|
||||||
let url = "/api/report/get/" + this.reportId;
|
|
||||||
this.$get(url, response => {
|
|
||||||
this.report = response.data || {};
|
|
||||||
if (this.report.status == 'Completed' || this.report.status == 'Success' || this.report.status == 'Error') {
|
|
||||||
this.content = JSON.parse(this.report.content);
|
|
||||||
this.getFails();
|
|
||||||
this.loading = false;
|
|
||||||
} else {
|
|
||||||
setTimeout(this.getReport, 2000)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getFails() {
|
|
||||||
this.fails = [];
|
|
||||||
this.content.scenarios.forEach((scenario) => {
|
|
||||||
let failScenario = Object.assign({}, scenario);
|
|
||||||
if (scenario.error > 0) {
|
|
||||||
this.fails.push(failScenario);
|
|
||||||
failScenario.requestResults = [];
|
|
||||||
scenario.requestResults.forEach((request) => {
|
|
||||||
if (!request.success) {
|
|
||||||
let failRequest = Object.assign({}, request);
|
|
||||||
failScenario.requestResults.push(failRequest);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.report-container .el-tabs__header {
|
|
||||||
margin-bottom: 1px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.report-container {
|
|
||||||
height: calc(100vh - 155px);
|
|
||||||
min-height: 600px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-header {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-header a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-header .time {
|
|
||||||
color: #909399;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-container .fail {
|
|
||||||
color: #F56C6C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-container .is-active .fail {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit cc38137a69a0f20fadece9c0f9f50a9468c4ace9
|
Subproject commit 06d935cd1d22ab36f09763745c2aff8ad3fb08c1
|
|
@ -10,7 +10,7 @@ const options = function (value, array) {
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const timestampFormatDate = function (timestamp) {
|
const timestampFormatDate = function (timestamp, showMs) {
|
||||||
if (!timestamp) {
|
if (!timestamp) {
|
||||||
return timestamp
|
return timestamp
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,14 @@ const timestampFormatDate = function (timestamp) {
|
||||||
let s = date.getSeconds();
|
let s = date.getSeconds();
|
||||||
s = s < 10 ? ('0' + s) : s;
|
s = s < 10 ? ('0' + s) : s;
|
||||||
|
|
||||||
return y + '-' + MM + '-' + d + ' ' + h + ':' + m + ':' + s
|
let format = y + '-' + MM + '-' + d + ' ' + h + ':' + m + ':' + s;
|
||||||
|
|
||||||
|
if (showMs === true) {
|
||||||
|
let ms = date.getMilliseconds();
|
||||||
|
format += ':' + ms
|
||||||
|
}
|
||||||
|
|
||||||
|
return format
|
||||||
};
|
};
|
||||||
|
|
||||||
const filters = {
|
const filters = {
|
||||||
|
|
|
@ -255,7 +255,7 @@ export function exportPdf(name, canvasList) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pdf.save(name + '.pdf');
|
pdf.save(name.replace(" ", "_") + '.pdf');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -468,6 +468,7 @@ export default {
|
||||||
input_url: "Please enter the request URL",
|
input_url: "Please enter the request URL",
|
||||||
input_path: "Please enter the request path",
|
input_path: "Please enter the request path",
|
||||||
name: "Name",
|
name: "Name",
|
||||||
|
content_type: "Content Type",
|
||||||
method: "Method",
|
method: "Method",
|
||||||
url: "URL",
|
url: "URL",
|
||||||
path: "Path",
|
path: "Path",
|
||||||
|
@ -597,6 +598,7 @@ export default {
|
||||||
scenario_name: "Scenario name",
|
scenario_name: "Scenario name",
|
||||||
response_time: "Response time(ms)",
|
response_time: "Response time(ms)",
|
||||||
latency: "Latency",
|
latency: "Latency",
|
||||||
|
start_time: "Start Time",
|
||||||
request_size: "Request Size",
|
request_size: "Request Size",
|
||||||
response_size: "Response Size",
|
response_size: "Response Size",
|
||||||
response_code: "Response Code",
|
response_code: "Response Code",
|
||||||
|
|
|
@ -468,6 +468,7 @@ export default {
|
||||||
input_url: "请输入请求URL",
|
input_url: "请输入请求URL",
|
||||||
input_path: "请输入请求路径",
|
input_path: "请输入请求路径",
|
||||||
name: "请求名称",
|
name: "请求名称",
|
||||||
|
content_type: "请求类型",
|
||||||
method: "请求方法",
|
method: "请求方法",
|
||||||
url: "请求URL",
|
url: "请求URL",
|
||||||
path: "请求路径",
|
path: "请求路径",
|
||||||
|
@ -597,6 +598,7 @@ export default {
|
||||||
delete_confirm: '确认删除报告: ',
|
delete_confirm: '确认删除报告: ',
|
||||||
delete_batch_confirm: '确认批量删除报告',
|
delete_batch_confirm: '确认批量删除报告',
|
||||||
scenario_name: "场景名称",
|
scenario_name: "场景名称",
|
||||||
|
start_time: "开始时间",
|
||||||
response_time: "响应时间(ms)",
|
response_time: "响应时间(ms)",
|
||||||
latency: "网络延迟",
|
latency: "网络延迟",
|
||||||
request_size: "请求大小",
|
request_size: "请求大小",
|
||||||
|
|
|
@ -470,6 +470,7 @@ export default {
|
||||||
name: "請求名稱",
|
name: "請求名稱",
|
||||||
method: "請求方法",
|
method: "請求方法",
|
||||||
url: "請求URL",
|
url: "請求URL",
|
||||||
|
content_type: "請求類型",
|
||||||
path: "請求路徑",
|
path: "請求路徑",
|
||||||
address: "請求地址",
|
address: "請求地址",
|
||||||
refer_to_environment: "引用環境",
|
refer_to_environment: "引用環境",
|
||||||
|
@ -597,6 +598,7 @@ export default {
|
||||||
delete_confirm: '確認刪除報告: ',
|
delete_confirm: '確認刪除報告: ',
|
||||||
delete_batch_confirm: '確認批量刪除報告',
|
delete_batch_confirm: '確認批量刪除報告',
|
||||||
scenario_name: "場景名稱",
|
scenario_name: "場景名稱",
|
||||||
|
start_time: "開始時間",
|
||||||
response_time: "響應時間(ms)",
|
response_time: "響應時間(ms)",
|
||||||
latency: "網絡延遲",
|
latency: "網絡延遲",
|
||||||
request_size: "請求大小",
|
request_size: "請求大小",
|
||||||
|
|
Loading…
Reference in New Issue