feat: 测试计划报告导出html

This commit is contained in:
chenjianxing 2021-08-16 18:29:59 +08:00 committed by jianxing
parent d53844ef15
commit 3021e87f4d
26 changed files with 343 additions and 263 deletions

View File

@ -13,4 +13,6 @@ public class TestPlanFailureApiDTO extends TestPlanApiCaseDTO {
private String projectName;
private String caseId;
private String response;
}

View File

@ -12,4 +12,6 @@ public class TestPlanFailureScenarioDTO extends ApiScenarioDTO {
private String projectName;
private String caseId;
private APIScenarioReportResult response;
}

View File

@ -1,12 +1,18 @@
package io.metersphere.track.dto;
import io.metersphere.api.dto.automation.TestPlanFailureApiDTO;
import io.metersphere.api.dto.automation.TestPlanFailureScenarioDTO;
import io.metersphere.base.domain.IssuesDao;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TestPlanSimpleReportDTO {
private String name;
private Long startTime;
private Long endTime;
private int caseCount;
@ -15,7 +21,14 @@ public class TestPlanSimpleReportDTO {
private double executeRate;
private double passRate;
private String summary;
private Boolean isThirdPartIssue;
private TestPlanFunctionResultReportDTO functionResult;
private TestPlanApiResultReportDTO apiResult;
private TestPlanLoadResultReportDTO loadResult;
List<TestPlanCaseDTO> failureTestCases;
List<IssuesDao> issueList;
List<TestPlanFailureApiDTO> apiFailureResult;
List<TestPlanFailureScenarioDTO> scenarioFailureResult;
List<TestPlanLoadCaseDTO> loadFailureTestCases;
}

View File

@ -7,14 +7,14 @@ import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.automation.*;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
import io.metersphere.api.dto.definition.request.*;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.api.service.*;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.*;
@ -23,6 +23,7 @@ import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.*;
import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.dto.IssueTemplateDao;
import io.metersphere.i18n.Translator;
import io.metersphere.log.utils.ReflexObjectUtil;
import io.metersphere.log.vo.DetailColumn;
@ -32,6 +33,7 @@ import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.performance.request.RunTestPlanRequest;
import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.service.IssueTemplateService;
import io.metersphere.service.ScheduleService;
import io.metersphere.service.SystemParameterService;
import io.metersphere.track.Factory.ReportComponentFactory;
@ -99,8 +101,6 @@ public class TestPlanService {
@Resource
TestCaseReportMapper testCaseReportMapper;
@Resource
TestCaseReportService testCaseReportService;
@Resource
TestPlanProjectService testPlanProjectService;
@Resource
ProjectMapper projectMapper;
@ -155,6 +155,12 @@ public class TestPlanService {
@Lazy
@Resource
private IssuesService issuesService;
@Resource
private ApiScenarioReportService apiScenarioReportService;
@Resource
private ApiDefinitionService apiDefinitionService;
@Resource
private IssueTemplateService issueTemplateService;
public synchronized String addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
@ -1343,22 +1349,37 @@ public class TestPlanService {
}
public void exportPlanReport(String planId, HttpServletResponse response) throws UnsupportedEncodingException {
TestPlan testPlan = getTestPlan(planId);
if (StringUtils.isBlank(testPlan.getReportId())) {
MSException.throwException("请先创建报告");
}
TestCaseReport testCaseReport = testCaseReportService.getTestCaseReport(testPlan.getReportId());
String content = testCaseReport.getContent();
JSONArray components = JSONObject.parseObject(content).getJSONArray("components");
List<TestPlanPreviewsDTO.Preview> previews = new ArrayList<>();
components.forEach(item -> {
previews.add(TestPlanPreviewsDTO.get((Integer) item));
TestPlanSimpleReportDTO report = getReport(planId);
List<TestPlanCaseDTO> failureTestCases = testPlanTestCaseService.getFailureCases(planId);
report.setFailureTestCases(failureTestCases);
List<IssuesDao> issueList = issuesService.getIssuesByPlanoId(planId);
report.setIssueList(issueList);
List<TestPlanFailureApiDTO> apiFailureResult = testPlanApiCaseService.getFailureList(planId);
apiFailureResult.forEach(item -> {
APIReportResult dbResult = apiDefinitionService.getDbResult(item.getId());
if (dbResult != null && StringUtils.isNotBlank(dbResult.getContent())) {
item.setResponse(dbResult.getContent());
}
});
TestCaseReportMetricDTO metric = getMetric(planId);
render(previews, metric, response);
report.setApiFailureResult(apiFailureResult);
List<TestPlanFailureScenarioDTO> scenarioFailureResult = testPlanScenarioCaseService.getFailureList(planId);
scenarioFailureResult.forEach((item) -> {
item.setResponse(apiScenarioReportService.get(item.getReportId()));
});
report.setScenarioFailureResult(scenarioFailureResult);
List<TestPlanLoadCaseDTO> loadFailureTestCases = testPlanLoadCaseService.getFailureCases(planId);
report.setLoadFailureTestCases(loadFailureTestCases);
render(report, response);
}
public void render(List<TestPlanPreviewsDTO.Preview> previews, TestCaseReportMetricDTO metric, HttpServletResponse response) throws UnsupportedEncodingException {
public void render(TestPlanSimpleReportDTO report, HttpServletResponse response) throws UnsupportedEncodingException {
response.reset();
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("test", StandardCharsets.UTF_8.name()));
@ -1368,8 +1389,8 @@ public class TestPlanService {
BufferedReader bufferedReader = new BufferedReader(isr);
String line = null;
while (null != (line = bufferedReader.readLine())) {
line = line.replace("\"#metric\"", JSONObject.toJSONString(metric));
line = line.replace("\"#preview\"", JSONObject.toJSONString(previews));
line = line.replace("\"#report\"", JSONObject.toJSONString(report));
// line = line.replace("\"#preview\"", JSONObject.toJSONString(previews));
line += "\n";
byte[] lineBytes = line.getBytes(StandardCharsets.UTF_8);
int start = 0;
@ -1399,6 +1420,7 @@ public class TestPlanService {
report.setStartTime(testPlan.getActualStartTime());
report.setStartTime(testPlan.getActualEndTime());
report.setSummary(testPlan.getReportSummary());
IssueTemplateDao template = issueTemplateService.getTemplate(testPlan.getProjectId());
testPlanTestCaseService.calculatePlanReport(planId, report);
issuesService.calculatePlanReport(planId, report);
testPlanApiCaseService.calculatePlanReport(planId, report);
@ -1406,6 +1428,12 @@ public class TestPlanService {
testPlanLoadCaseService.calculatePlanReport(planId, report);
report.setExecuteRate(report.getExecuteCount() * 0.1 / report.getCaseCount());
report.setPassRate(report.getPassCount() * 0.1 / report.getCaseCount());
report.setName(testPlan.getName());
if (template == null || template.getPlatform().equals("metersphere")) {
report.setIsThirdPartIssue(false);
} else {
report.setIsThirdPartIssue(true);
}
return report;
}

View File

@ -3,7 +3,7 @@
<ms-main-container>
<el-card>
<section class="report-container" v-if="this.report.testId">
<ms-api-report-view-header :debug="debug" :report="report" @reportExport="handleExport" @reportSave="handleSave"/>
<ms-api-report-view-header :is-template="isTemplate" :debug="debug" :report="report" @reportExport="handleExport" @reportSave="handleSave"/>
<main v-if="isNotRunning">
<ms-metric-chart :content="content" :totalTime="totalTime"/>
<div>
@ -76,10 +76,19 @@ export default {
currentProjectId: String,
infoDb: Boolean,
debug: Boolean,
isTemplate: Boolean,
templateReport: Object,
},
watch: {
reportId() {
this.getReport();
if (!this.isTemplate) {
this.getReport();
}
},
templateReport() {
if (this.isTemplate) {
this.getReport();
}
}
},
methods: {
@ -225,32 +234,41 @@ export default {
},
getReport() {
this.init();
if (this.reportId) {
let url = "/api/scenario/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);
if (!this.content) {
this.content = {scenarios: []};
}
this.formatResult(this.content);
} catch (e) {
throw e;
}
this.getFails();
this.computeTotalTime();
this.loading = false;
} else {
setTimeout(this.getReport, 2000)
if (this.isTemplate) {
//
this.report = this.templateReport;
this.buildReport();
} else {
if (this.reportId) {
let url = "/api/scenario/report/get/" + this.reportId;
this.$get(url, response => {
this.report = response.data || {};
this.buildReport();
});
}
}
},
buildReport() {
if (this.report) {
if (this.isNotRunning) {
try {
this.content = JSON.parse(this.report.content);
if (!this.content) {
this.content = {scenarios: []};
}
} else {
this.loading = false;
this.$error(this.$t('api_report.not_exist'));
this.formatResult(this.content);
} catch (e) {
throw e;
}
});
this.getFails();
this.computeTotalTime();
this.loading = false;
} else {
setTimeout(this.getReport, 2000)
}
} else {
this.loading = false;
this.$error(this.$t('api_report.not_exist'));
}
},
getFails() {

View File

@ -17,7 +17,7 @@
</span>
<span class="time"> {{ report.createTime | timestampFormatDate }}</span>
<el-button v-if="!debug || exportFlag" v-permission="['PROJECT_API_REPORT:READ+EXPORT']" :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)" style="margin-right: 10px">
<el-button v-if="(!debug || exportFlag) && !isTemplate" v-permission="['PROJECT_API_REPORT:READ+EXPORT']" :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)" style="margin-right: 10px">
{{ $t('test_track.plan_view.export_report') }}
</el-button>
@ -33,6 +33,7 @@ export default {
props: {
report: {},
debug: Boolean,
isTemplate: Boolean,
exportFlag: {
type: Boolean,
default: false,

View File

@ -4,20 +4,20 @@
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_code')}} :</div>
<el-tooltip
:content="response.responseResult.responseCode"
:content="responseResult.responseCode"
placement="top">
<div class="node-title">
{{response.responseResult && response.responseResult.responseCode ? response.responseResult.responseCode :'0'}}
{{responseResult && responseResult.responseCode ? responseResult.responseCode :'0'}}
</div>
</el-tooltip>
</el-col>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_time')}} :</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult && response.responseResult.responseTime?response.responseResult.responseTime:0}} ms</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{responseResult && responseResult.responseTime?responseResult.responseTime:0}} ms</div>
</el-col>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_size')}} :</div>
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">{{response.responseResult && response.responseResult.responseSize?response.responseResult.responseSize:0}} bytes</div>
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">{{responseResult && responseResult.responseSize?responseResult.responseSize:0}} bytes</div>
</el-col>
</el-row>
</div>
@ -28,10 +28,19 @@
name: "MsRequestMetric",
props: {
response: Object
response: {
type: Object,
default() {
return {}
}
}
},
computed: {
responseResult() {
return this.response && this.response.responseResult ? this.response.responseResult : {};
},
error() {
return this.response && this.response.responseCode && this.response.responseCode >= 400;
}

View File

@ -3,24 +3,24 @@
<el-tabs v-model="activeName" v-show="isActive">
<el-tab-pane :label="$t('api_test.definition.request.response_body')" name="body" class="pane">
<ms-sql-result-table v-if="isSqlType" :body="response.responseResult.body"/>
<ms-code-edit v-if="!isSqlType && isMsCodeEditShow" :mode="mode" :read-only="true" :modes="modes" :data.sync="response.responseResult.body" ref="codeEdit"/>
<ms-sql-result-table v-if="isSqlType" :body="responseResult.body"/>
<ms-code-edit v-if="!isSqlType && isMsCodeEditShow" :mode="mode" :read-only="true" :modes="modes" :data.sync="responseResult.body" ref="codeEdit"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane">
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="response.responseResult.headers"/>
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="responseResult.headers"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.console')" name="console" class="pane">
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="response.responseResult.console"/>
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="responseResult.console"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
<ms-assertion-results :assertions="response.responseResult.assertions"/>
<ms-assertion-results :assertions="responseResult.assertions"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="response.responseResult.vars"/>
<ms-code-edit :mode="'text'" :read-only="true" :data.sync="responseResult.vars"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_report.request_body')" name="request_body" class="pane">
@ -136,6 +136,9 @@
computed: {
isSqlType() {
return (this.currentProtocol === "SQL" && this.response.responseResult.responseCode === '200' && this.mode === 'table');
},
responseResult() {
return this.response && this.response.responseResult ? this.response.responseResult : {};
}
}
}

View File

@ -24,7 +24,7 @@ export default {
props: {
prop: String,
label: String,
width: String,
width: [String, Number],
minWidth: [String, Number],
fixed: String,
// mapperfilters

View File

@ -1,121 +1,29 @@
<template>
<div v-loading="result.loading">
<el-row type="flex" class="head-bar">
<el-col :span="12">
</el-col>
<el-col :span="11" class="head-right">
<!-- <el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleExport(report.name)">-->
<!-- {{$t('test_track.plan_view.export_report')}}-->
<!-- </el-button>-->
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleExportHtml(report.name)">
{{'导出HTML'}}
</el-button>
</el-col>
</el-row>
<div class="container" ref="resume" id="app">
<div>
<div class="container">
<el-main>
<test-plan-report-content :plan-id="planId"/>
<test-plan-report-content :is-template="isTemplate" :plan-id="planId" ref="reportContent"/>
</el-main>
</div>
</div>
</template>
<script>
import {exportPdf} from "@/common/js/utils";
import BaseInfoComponent from "./TemplateComponent/BaseInfoComponent";
import TestResultChartComponent from "./TemplateComponent/TestResultChartComponent";
import TestResultComponent from "./TemplateComponent/TestResultComponent";
import RichTextComponent from "./TemplateComponent/RichTextComponent";
import TestCaseReportTemplateEdit from "./TestCaseReportTemplateEdit";
import TemplateComponent from "./TemplateComponent/TemplateComponent";
import html2canvas from "html2canvas";
import MsTestCaseReportExport from "../TestCaseReportExport";
import TestReportTemplateList from "../TestReportTemplateList";
import TestPlanReportContent from "@/business/components/track/plan/view/comonents/report/detail/TestPlanReportContent";
export default {
name: "TestPlanDetailReport",
components: {
TestPlanReportContent,
TestReportTemplateList,
MsTestCaseReportExport,
TemplateComponent,
TestCaseReportTemplateEdit,
RichTextComponent, TestResultComponent, TestResultChartComponent, BaseInfoComponent
},
data() {
return {
result: {},
imgUrl: "",
previews: [],
report: {},
metric: {},
reportExportVisible: false,
isTestManagerOrTestUser: false
}
},
mounted() {
this.isTestManagerOrTestUser = true;
},
computed: {
planId() {
return this.testPlan.id;
},
},
props: ['testPlan'],
methods: {
handleExport(name) {
this.result.loading = true;
this.reportExportVisible = true;
let reset = this.exportReportReset;
this.$nextTick(function () {
setTimeout(() => {
html2canvas(document.getElementById('testCaseReportExport'), {
scale: 2
}).then(function(canvas) {
exportPdf(name, [canvas]);
reset();
});
}, 1000);
});
},
handleExportHtml(name) {
let config = {
url: '/test/plan/report/export/' + this.planId,
method: 'get',
responseType: 'blob'
};
this.$download(config, name + '.html');
},
exportReportReset() {
this.reportExportVisible = false;
this.result.loading = false;
},
}
props: ['testPlan', 'isTemplate'],
}
</script>cd
</script>
<style scoped>
.head-right {
text-align: right;
}
.head-bar .el-button {
margin-bottom: 10px;
width: 80px;
}
.el-button+.el-button {
margin-left: 0px;
}
.head-bar {
position: fixed;
right: 10px;
padding: 20px;
}
</style>

View File

@ -5,7 +5,7 @@
<api-result :api-result="report.apiResult"/>
</el-tab-pane>
<el-tab-pane label="失败用例" name="second">
<api-failure-result :plan-id="planId"/>
<api-failure-result :report="report" :is-template="isTemplate" :plan-id="planId"/>
</el-tab-pane>
<!-- <el-tab-pane label="所有用例" name="fourth">所有用例</el-tab-pane>-->
@ -28,7 +28,7 @@ export default {
};
},
props: [
'report', 'planId'
'report', 'planId', 'isTemplate'
],
methods: {
handleClick(tab, event) {

View File

@ -5,10 +5,10 @@
<functional-result :function-result="report.functionResult"/>
</el-tab-pane>
<el-tab-pane label="失败用例" name="second">
<functional-failure-result :plan-id="planId"/>
<functional-failure-result :is-template="isTemplate" :report="report" :plan-id="planId"/>
</el-tab-pane>
<el-tab-pane label="缺陷列表" name="third">
<functional-issue-list :plan-id="planId"/>
<functional-issue-list :is-template="isTemplate" :report="report" :plan-id="planId"/>
</el-tab-pane>
<!-- <el-tab-pane label="所有用例" name="fourth">所有用例</el-tab-pane>-->
</el-tabs>
@ -33,7 +33,7 @@ export default {
};
},
props: [
'report','planId'
'report','planId', 'isTemplate'
],
methods: {
handleClick(tab, event) {

View File

@ -5,7 +5,7 @@
<load-result :load-result="report.loadResult"/>
</el-tab-pane>
<el-tab-pane label="失败用例" name="second">
<load-failure-result :plan-id="planId"/>
<load-failure-result :is-template="isTemplate" :report="report" :plan-id="planId"/>
</el-tab-pane>
<!-- <el-tab-pane label="所有用例" name="fourth">所有用例</el-tab-pane>-->
</el-tabs>
@ -33,7 +33,8 @@ export default {
},
props: [
'report',
'planId'
'planId',
'isTemplate'
],
methods: {
handleClick(tab, event) {

View File

@ -1,9 +1,19 @@
<template>
<el-card>
<test-plan-report-header :report="report" :plan-id="planId"/>
<test-plan-functional-report v-if="functionalEnable" :plan-id="planId" :report="report"/>
<test-plan-api-report v-if="apiEnable" :report="report" :plan-id="planId"/>
<test-plan-load-report v-if="loadEnable" :report="report" :plan-id="planId"/>
<el-card v-loading="result.loading">
<!-- <el-row v-if="!isTemplate" type="flex" class="head-bar">-->
<div v-if="!isTemplate" class="head-bar head-right">
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleExportHtml()">
{{'导出HTML'}}
</el-button>
</div>
<!-- </el-row>-->
<test-plan-report-header :is-template="isTemplate" :report="report" :plan-id="planId"/>
<test-plan-functional-report :is-template="isTemplate" v-if="functionalEnable" :plan-id="planId" :report="report"/>
<test-plan-api-report :is-template="isTemplate" v-if="apiEnable" :report="report" :plan-id="planId"/>
<test-plan-load-report :is-template="isTemplate" v-if="loadEnable" :report="report" :plan-id="planId"/>
</el-card>
</template>
@ -16,34 +26,54 @@ import TestPlanApiReport from "@/business/components/track/plan/view/comonents/r
import TestPlanLoadReport from "@/business/components/track/plan/view/comonents/report/detail/TestPlanLoadReport";
export default {
name: "TestPlanReportContent",
components: {TestPlanLoadReport, TestPlanApiReport, TestPlanFunctionalReport, TestPlanReportHeader},
components: {
TestPlanLoadReport,
TestPlanApiReport,
TestPlanFunctionalReport,
TestPlanReportHeader},
props: {
planId:String,
isTemplate: Boolean
},
data() {
return {
report: {}
report: {},
result: {},
isTestManagerOrTestUser: false
};
},
created() {
this.isTestManagerOrTestUser = true;
this.getReport();
},
computed: {
functionalEnable() {
return this.report.functionResult.caseData.length > 0;
return this.report.functionResult && this.report.functionResult.caseData.length > 0;
},
apiEnable() {
return this.report.apiResult.apiCaseData.length > 0 || this.report.apiResult.apiScenarioData.length > 0;
return this.report.apiResult && (this.report.apiResult.apiCaseData.length > 0 || this.report.apiResult.apiScenarioData.length) > 0;
},
loadEnable() {
return this.report.loadResult.caseData.length > 0;
return this.report.loadResult && this.report.loadResult.caseData.length > 0;
}
},
methods: {
getReport() {
this.result = getTestPlanReport(this.planId, (data) => {
this.report = data;
});
if (this.isTemplate) {
this.report = "#report";
} else {
this.result = getTestPlanReport(this.planId, (data) => {
this.report = data;
});
}
},
handleExportHtml() {
let config = {
url: '/test/plan/report/export/' + this.planId,
method: 'get',
responseType: 'blob'
};
this.result = this.$download(config, this.report.name + '.html');
},
}
}
@ -61,4 +91,26 @@ export default {
padding-right: 15px;
padding-top: 15px;
}
.head-right {
text-align: right;
}
.head-bar .el-button {
margin-bottom: 10px;
width: 80px;
margin-right: 10px;
}
.el-button+.el-button {
margin-left: 0px;
}
/*.head-bar {*/
/* position: fixed;*/
/* right: 10px;*/
/* padding: 20px;*/
/*}*/
</style>

View File

@ -22,7 +22,7 @@
</el-col>
</el-row>
<el-form-item :label="'报告总结'">
<el-link @click="isEdit = true">
<el-link v-if="!isTemplate" @click="isEdit = true">
编辑
</el-link>
</el-form-item>
@ -52,7 +52,8 @@ export default {
components: {TestPlanReportContainer, MsFormDivider},
props: {
planId: String,
report: Object
report: Object,
isTemplate: Boolean
},
data() {
return {

View File

@ -69,13 +69,14 @@ export default {
MsRequestResultTail,
MsTableColumn, MsTable, StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem},
props: {
planId: String
planId: String,
isTemplate: Boolean,
report: Object
},
data() {
return {
apiCases: [],
result: {},
report: {},
response: {}
}
},
@ -84,17 +85,28 @@ export default {
},
methods: {
getScenarioApiCase() {
this.result = getPlanApiFailureCase(this.planId, (data) => {
this.apiCases = data;
if (data && data.length > 0) {
this.rowClick(data[0]);
if (this.isTemplate) {
this.apiCases = this.report.apiFailureResult;
if (this.apiCases && this.apiCases.length > 0) {
this.rowClick(this.apiCases[0]);
}
});
} else {
this.result = getPlanApiFailureCase(this.planId, (data) => {
this.apiCases = data;
if (data && data.length > 0) {
this.rowClick(data[0]);
}
});
}
},
rowClick(row) {
getApiReport(row.id, (data) => {
this.response = JSON.parse(data.content);
});
if (this.isTemplate) {
this.response = JSON.parse(row.response);
} else {
getApiReport(row.id, (data) => {
this.response = JSON.parse(data.content);
});
}
}
}
}

View File

@ -2,10 +2,10 @@
<div>
<el-tabs type="card">
<el-tab-pane label="接口用例">
<api-case-failure-result :plan-id="planId"/>
<api-case-failure-result :report="report" :is-template="isTemplate" :plan-id="planId"/>
</el-tab-pane>
<el-tab-pane label="场景用例">
<api-scenario-failure-result :plan-id="planId"/>
<api-scenario-failure-result :report="report" :is-template="isTemplate" :plan-id="planId"/>
</el-tab-pane>
</el-tabs>
</div>
@ -26,7 +26,9 @@ export default {
ApiCaseFailureResult,
ApiScenarioFailureResult, StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem},
props: {
planId: String
planId: String,
isTemplate: Boolean,
report: {}
},
data() {
return {

View File

@ -17,26 +17,20 @@
:label="$t('commons.id')"
prop="customNum">
</ms-table-column>
<ms-table-column
:label="$t('commons.name')"
prop="name">
</ms-table-column>
<ms-table-column
:label="'创建人'"
prop="creatorName">
prop="creatorName"/>
<ms-table-column
:label="$t('test_track.case.priority')"
:width="80"
prop="level">
:width="80">
<template v-slot:default="scope">
<priority-table-item :value="scope.row.level" ref="priority"/>
</template>
</ms-table-column>
</ms-table-column>
<ms-table-column
:width="70"
:label="'步骤数'"
@ -51,7 +45,7 @@
</ms-table>
</el-col>
<el-col :span="16" v-if="scenarioCases.length > 0">
<ms-api-report @refresh="search" :infoDb="true" :report-id="reportId"/>
<ms-api-report :template-report="response" :is-template="isTemplate" :infoDb="true" :report-id="reportId"/>
</el-col>
</el-row>
</div>
@ -72,14 +66,16 @@ export default {
MsApiReport,
MsTableColumn, MsTable, StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem},
props: {
planId: String
planId: String,
isTemplate: Boolean,
report: Object
},
data() {
return {
scenarioCases: [],
result: {},
report: {},
reportId: null
reportId: null,
response: {}
}
},
mounted() {
@ -87,15 +83,26 @@ export default {
},
methods: {
getScenarioApiCase() {
this.result = getPlanScenarioFailureCase(this.planId, (data) => {
this.scenarioCases = data;
if (data && data.length > 0) {
this.reportId = data[0].reportId;
if (this.isTemplate) {
this.scenarioCases = this.report.scenarioFailureResult;
if (this.scenarioCases && this.scenarioCases.length > 0) {
this.rowClick(this.scenarioCases[0]);
}
});
} else {
this.result = getPlanScenarioFailureCase(this.planId, (data) => {
this.scenarioCases = data;
if (data && data.length > 0) {
this.reportId = data[0].reportId;
}
});
}
},
rowClick(row) {
this.reportId = row.reportId;
if (this.isTemplate) {
this.response = row.response;
} else {
this.reportId = row.reportId;
}
}
}
}

View File

@ -74,7 +74,9 @@ export default {
name: "FunctionalFailureResult",
components: {StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem},
props: {
planId: String
planId: String,
isTemplate: Boolean,
report: {}
},
data() {
return {
@ -86,9 +88,13 @@ export default {
},
methods: {
getFailureTestCase() {
getPlanFunctionFailureCase(this.planId, (data) => {
this.failureTestCases = data;
});
if (this.isTemplate) {
this.failureTestCases = this.report.failureTestCases;
} else {
getPlanFunctionFailureCase(this.planId, (data) => {
this.failureTestCases = data;
});
}
}
}
}

View File

@ -65,17 +65,20 @@ export default {
return {
data: [],
result: {},
isThirdPart: false
isThirdPart: false,
}
},
props: ['planId'],
props: ['planId', 'isTemplate', 'report'],
computed: {
issueStatusMap() {
return ISSUE_STATUS_MAP;
},
},
mounted() {
getIssueTemplate()
if (this.isTemplate) {
this.isThirdPart = this.report.isThirdPartIssue;
} else {
getIssueTemplate()
.then((template) => {
if (template.platform === 'metersphere') {
this.isThirdPart = false;
@ -83,13 +86,18 @@ export default {
this.isThirdPart = true;
}
});
}
this.getIssues();
},
methods: {
getIssues() {
this.result = getIssuesByPlanId(this.planId, (data) => {
this.data = data;
});
if (this.isTemplate) {
this.data = this.report.issueList;
} else {
this.result = getIssuesByPlanId(this.planId, (data) => {
this.data = data;
});
}
},
}
}

View File

@ -45,7 +45,9 @@ export default {
name: "LoadFailureResult",
components: {StatusTableItem, MethodTableItem, TypeTableItem},
props: {
planId: String
planId: String,
report: Object,
isTemplate: Boolean
},
data() {
return {
@ -57,9 +59,13 @@ export default {
},
methods: {
getFailureTestCase() {
getPlanLoadFailureCase(this.planId, (data) => {
this.failureTestCases = data;
});
if (this.isTemplate) {
this.failureTestCases = this.report.loadFailureTestCases;
} else {
getPlanLoadFailureCase(this.planId, (data) => {
this.failureTestCases = data;
});
}
}
}
}

View File

@ -146,8 +146,9 @@ export function fileUpload(url, file, files, param, success, failure) {
return request(axiosRequestConfig, success, failure);
}
export function download(config, fileName) {
return this.$request(config).then(response => {
export function download(config, fileName, success) {
let result = {loading: true};
this.$request(config).then(response => {
const content = response.data;
const blob = new Blob([content], {type: "application/octet-stream"});
if ("download" in document.createElement("a")) {
@ -158,11 +159,15 @@ export function download(config, fileName) {
aTag.href = URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(aTag.href);
then(success, response, result);
} else {
// IE10+下载
navigator.msSaveBlob(blob, this.filename);
}
}).catch(error => {
exception(error, result, "");
});
return result;
}
export function all(array, callback) {

View File

@ -12,8 +12,8 @@ export function getScenarioReport(reportId, callback) {
return reportId ? baseGet('/api/scenario/report/get/' + reportId, callback) : {};
}
export function getApiReport(reportId, callback) {
return reportId ? baseGet('/api/definition/report/getReport/' + reportId, callback) : {};
export function getApiReport(testId, callback) {
return testId ? baseGet('/api/definition/report/getReport/' + testId, callback) : {};
}

View File

@ -1,33 +0,0 @@
<template>
<div class="container" ref="resume" id="app">
<el-main>
<div v-for="(item, index) in preview" :key="item.id">
<template-component :isReportView="true" :metric="metric" :preview="item" :index="index" ref="templateComponent"/>
</div>
</el-main>
</div>
</template>
<script>
import TemplateComponent
from "@/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent";
export default {
name: "PlanReport",
components: {TemplateComponent},
data() {
return {
preview: [{"name":"基础信息","id":1,"type":"system"},{"name":"测试结果","id":2,"type":"system"},{"name":"测试结果分布","id":3,"type":"system"},{"name":"失败用例","id":4,"type":"system"},{"name":"缺陷列表","id":5,"type":"system"}]
,metric: {"executeResult":{"functionalResult":[{"status":"Failure","count":1},{"status":"Prepare","count":4}],"apiResult":[{"status":"Prepare","count":13}],"scenarioResult":[],"loadResult":[],"executedScenarioIds":null},"moduleExecuteResult":[{"moduleId":"d0785ec7-46b3-441d-a5ff-aff13f055241","moduleName":"默认模块","caseCount":1,"passCount":0,"passRate":0,"issuesCount":0,"prepareCount":1,"skipCount":0,"failureCount":0,"blockingCount":0,"underwayCount":0,"projectName":"Local"},{"moduleId":"2f477110-0976-4d4a-84a5-9b33b82f01e8","moduleName":"a","caseCount":1,"passCount":0,"passRate":0,"issuesCount":0,"prepareCount":1,"skipCount":0,"failureCount":0,"blockingCount":0,"underwayCount":0,"projectName":"jira"},{"moduleId":"8631f33c-222b-4c3e-b4b4-e643dacdfe70","moduleName":"默认模块","caseCount":2,"passCount":0,"passRate":0,"issuesCount":0,"prepareCount":2,"skipCount":0,"failureCount":0,"blockingCount":0,"underwayCount":0,"projectName":"jira"}],"failureTestCases":{"functionalTestCases":[{"id":"0ca40965-e40a-4956-9026-7a3604f71562","nodeId":"root","nodePath":"","projectId":null,"name":"copy_sdfadasa","type":"functional","maintainer":"tests","priority":"P0","method":"","createTime":1626075279957,"updateTime":1626348003718,"testId":"[]","sort":null,"num":null,"otherTestName":null,"reviewStatus":null,"tags":"[]","demandId":null,"demandName":null,"followPeople":null,"status":"Failure","customNum":"100002","stepModel":null,"createUser":null,"originalStatus":null,"deleteTime":null,"deleteUserId":null,"prerequisite":null,"remark":null,"steps":null,"stepDescription":null,"expectedResult":null,"customFields":"[{\"id\":\"0d2b90ec-9c56-4f5d-ae06-522a93f48e93\",\"name\":\"责任人\",\"value\":\"tests\",\"customData\":null},{\"id\":\"661e1e29-c401-43b0-972e-a713b7b90c37\",\"name\":\"用例等级\",\"value\":\"P0\",\"customData\":null},{\"id\":\"f448e8d2-fad1-4c23-a013-3f3ba282bb81\",\"name\":\"用例状态\",\"value\":\"Prepare\",\"customData\":null}]","executor":"admin","executorName":null,"results":null,"planId":"96791ef8-8a75-4335-a48c-832e0ddece5f","planName":null,"caseId":"9883f2f8-5e33-433d-9960-aec6698d167b","issues":"[{\"caseCount\":0,\"createTime\":1625057764241,\"creator\":\"admin\",\"customFields\":\"[{\\\"id\\\":\\\"52f64983-aa06-4bf2-bf7a-1ad6b48ad197\\\",\\\"name\\\":\\\"状态\\\",\\\"value\\\":\\\"resolved\\\",\\\"customData\\\":null}]\",\"description\":\"sdfsdf\",\"id\":\"1133268703001004718\",\"num\":100007,\"platform\":\"Tapd\",\"platformStatus\":\"新\",\"projectId\":\"8fed2fce-d4f0-11eb-a517-dce9941665c4\",\"status\":\"new\",\"title\":\"sdfsd\",\"updateTime\":1625057764241}]","reportId":null,"model":null,"projectName":"默认项目","actualResult":null,"maintainerName":null,"issuesCount":1,"list":null}],"apiTestCases":[],"scenarioTestCases":[],"loadTestCases":[]},"executors":["admin"],"executorNames":["Administrator"],"principal":"admin","principalName":"Administrator","startTime":null,"endTime":null,"projectName":"默认项目","issues":[{"id":"1133268703001004718","title":"sdfsd","status":"new","createTime":1625057764241,"updateTime":1625057764241,"reporter":null,"lastmodify":null,"platform":"Tapd","projectId":"8fed2fce-d4f0-11eb-a517-dce9941665c4","creator":"admin","resourceId":null,"num":100007,"platformStatus":"新","description":"sdfsdf","customFields":"[{\"id\":\"52f64983-aa06-4bf2-bf7a-1ad6b48ad197\",\"name\":\"状态\",\"value\":\"resolved\",\"customData\":null}]","model":"","projectName":"默认项目","creatorName":null,"resourceName":null,"caseCount":0,"caseIds":null}]}
// preview: "#preview",
// metric: "#metric"
}
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,27 @@
<template>
<div>
<el-main>
<test-plan-detail-report :is-template="true" :test-plan="testPlan"/>
</el-main>
</div>
</template>
<script>
import TestPlanDetailReport from "@/business/components/track/plan/view/comonents/report/TestPlanDetailReport";
export default {
name: "PlanReportTemplate",
components: {TestPlanDetailReport},
data() {
return {
testPlan: {"id": "96791ef8-8a75-4335-a48c-832e0ddece5f"}
}
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@ -3,10 +3,11 @@ import ElementUI, {Button, Col, Form, FormItem, Input, Row, Main, Card, Table, T
import '@/assets/theme/index.css';
import '@/common/css/menu-header.css';
import '@/common/css/main.css';
import PlanReport from "./PlanReport.vue";
import i18n from "@/i18n/i18n";
import chart from "@/common/js/chart";
import CKEditor from '@ckeditor/ckeditor5-vue';
// import CKEditor from '@ckeditor/ckeditor5-vue';
import PlanReportTemplate from "@/template/report/plan/PlanReportTemplate";
// import PlanReport from "@/template/report/plan/PlanReport";
Vue.use(ElementUI, {
@ -24,11 +25,12 @@ Vue.use(Main);
Vue.use(Card);
Vue.use(TableColumn);
Vue.use(Table);
Vue.use(CKEditor);
// Vue.use(CKEditor);
new Vue({
el: '#planReport',
i18n,
render: h => h(PlanReport)
// render: h => h(PlanReport)
render: h => h(PlanReportTemplate)
});