feat: 测试报告可配置

This commit is contained in:
chenjianxing 2021-08-21 13:33:26 +08:00 committed by jianxing
parent 17d924f9e9
commit 3c303e97e0
15 changed files with 306 additions and 44 deletions

View File

@ -13,5 +13,7 @@ public class TestPlanWithBLOBs extends TestPlan implements Serializable {
private String reportSummary;
private String reportConfig;
private static final long serialVersionUID = 1L;
}

View File

@ -26,6 +26,7 @@
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestPlanWithBLOBs">
<result column="tags" jdbcType="LONGVARCHAR" property="tags" />
<result column="report_summary" jdbcType="LONGVARCHAR" property="reportSummary" />
<result column="report_config" jdbcType="LONGVARCHAR" property="reportConfig" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -91,7 +92,7 @@
actual_start_time, actual_end_time, creator, project_id, execution_times, automatic_status_update
</sql>
<sql id="Blob_Column_List">
tags, report_summary
tags, report_summary, report_config
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.TestPlanExample" resultMap="ResultMapWithBLOBs">
select
@ -149,7 +150,7 @@
planned_start_time, planned_end_time, actual_start_time,
actual_end_time, creator, project_id,
execution_times, automatic_status_update, tags,
report_summary)
report_summary, report_config)
values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR},
#{stage,jdbcType=VARCHAR}, #{principal,jdbcType=VARCHAR}, #{testCaseMatchRule,jdbcType=VARCHAR},
@ -157,7 +158,7 @@
#{plannedStartTime,jdbcType=BIGINT}, #{plannedEndTime,jdbcType=BIGINT}, #{actualStartTime,jdbcType=BIGINT},
#{actualEndTime,jdbcType=BIGINT}, #{creator,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR},
#{executionTimes,jdbcType=INTEGER}, #{automaticStatusUpdate,jdbcType=BIT}, #{tags,jdbcType=LONGVARCHAR},
#{reportSummary,jdbcType=LONGVARCHAR})
#{reportSummary,jdbcType=LONGVARCHAR}, #{reportConfig,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestPlanWithBLOBs">
insert into test_plan
@ -228,6 +229,9 @@
<if test="reportSummary != null">
report_summary,
</if>
<if test="reportConfig != null">
report_config,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -296,6 +300,9 @@
<if test="reportSummary != null">
#{reportSummary,jdbcType=LONGVARCHAR},
</if>
<if test="reportConfig != null">
#{reportConfig,jdbcType=LONGVARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.TestPlanExample" resultType="java.lang.Long">
@ -373,6 +380,9 @@
<if test="record.reportSummary != null">
report_summary = #{record.reportSummary,jdbcType=LONGVARCHAR},
</if>
<if test="record.reportConfig != null">
report_config = #{record.reportConfig,jdbcType=LONGVARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -401,7 +411,8 @@
execution_times = #{record.executionTimes,jdbcType=INTEGER},
automatic_status_update = #{record.automaticStatusUpdate,jdbcType=BIT},
tags = #{record.tags,jdbcType=LONGVARCHAR},
report_summary = #{record.reportSummary,jdbcType=LONGVARCHAR}
report_summary = #{record.reportSummary,jdbcType=LONGVARCHAR},
report_config = #{record.reportConfig,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -498,6 +509,9 @@
<if test="reportSummary != null">
report_summary = #{reportSummary,jdbcType=LONGVARCHAR},
</if>
<if test="reportConfig != null">
report_config = #{reportConfig,jdbcType=LONGVARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -523,7 +537,8 @@
execution_times = #{executionTimes,jdbcType=INTEGER},
automatic_status_update = #{automaticStatusUpdate,jdbcType=BIT},
tags = #{tags,jdbcType=LONGVARCHAR},
report_summary = #{reportSummary,jdbcType=LONGVARCHAR}
report_summary = #{reportSummary,jdbcType=LONGVARCHAR},
report_config = #{reportConfig,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.TestPlan">

View File

@ -119,6 +119,13 @@ public class TestPlanController {
testPlanService.editTestPlanStatus(planId);
}
@PostMapping("/edit/report/config")
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_PLAN_READ_EDIT)
// @MsAuditLog(module = "track_test_plan", type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#planId)", content = "#msClass.getLogDetails(#planId)", msClass = TestPlanService.class)
public void editReportConfig(@RequestBody TestPlanDTO testPlanDTO) {
testPlanService.editReportConfig(testPlanDTO);
}
@PostMapping("/delete/{testPlanId}")
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_PLAN_READ_DELETE)
@MsAuditLog(module = "track_test_plan", type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#testPlanId)", msClass = TestPlanService.class)

View File

@ -21,6 +21,7 @@ public class TestPlanSimpleReportDTO {
private double executeRate;
private double passRate;
private String summary;
private String config;
private Boolean isThirdPartIssue;
private TestPlanFunctionResultReportDTO functionResult;
private TestPlanApiResultReportDTO apiResult;

View File

@ -1374,6 +1374,7 @@ public class TestPlanService {
report.setStartTime(testPlan.getActualStartTime());
report.setStartTime(testPlan.getActualEndTime());
report.setSummary(testPlan.getReportSummary());
report.setConfig(testPlan.getReportConfig());
IssueTemplateDao template = issueTemplateService.getTemplate(testPlan.getProjectId());
testPlanTestCaseService.calculatePlanReport(planId, report);
issuesService.calculatePlanReport(planId, report);
@ -1467,4 +1468,11 @@ public class TestPlanService {
List<String> planScenarioIds = testPlanApiScenarios.stream().map(TestPlanApiScenario::getId).collect(Collectors.toList());
testPlanScenarioCaseService.setScenarioEnv(planScenarioIds, envMap);
}
public void editReportConfig(TestPlanDTO testPlanDTO) {
TestPlanWithBLOBs testPlan = new TestPlanWithBLOBs();
testPlan.setId(testPlanDTO.getId());
testPlan.setReportConfig(testPlanDTO.getReportConfig());
testPlanMapper.updateByPrimaryKeySelective(testPlan);
}
}

View File

@ -94,3 +94,5 @@ ALTER TABLE api_document_share RENAME TO share_info;
ALTER TABLE share_info change
column share_api_id custom_data longtext CHARACTER
SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Share Custom Data';
ALTER TABLE test_plan ADD report_config text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '测试计划报告配置';

View File

@ -1,10 +1,10 @@
<template>
<test-plan-report-container :title="'接口用例统计'">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="测试结果" name="first">
<el-tab-pane v-if="resultEnable" label="测试结果" name="first">
<api-result :api-result="report.apiResult"/>
</el-tab-pane>
<el-tab-pane label="失败用例" name="second">
<el-tab-pane v-if="failureEnable" label="失败用例" name="second">
<api-failure-result :share-id="shareId" :is-share="isShare" :report="report" :is-template="isTemplate" :plan-id="planId"/>
</el-tab-pane>
@ -30,7 +30,35 @@ export default {
props: [
'report', 'planId', 'isTemplate', 'isShare', 'shareId'
],
computed: {
resultEnable() {
let disable = this.report.config && this.report.config.api.children.result.enable === false;
return !disable;
},
failureEnable() {
let disable = this.report.config && this.report.config.api.children.failure.enable === false;
return !disable;
},
},
watch: {
resultEnable() {
this.initActiveName();
},
failureEnable() {
this.initActiveName();
},
},
mounted() {
this.initActiveName();
},
methods: {
initActiveName() {
if (this.resultEnable) {
this.activeName = 'first';
} else if (this.failureEnable) {
this.activeName = 'second';
}
},
handleClick(tab, event) {
}
}

View File

@ -1,13 +1,13 @@
<template>
<test-plan-report-container :title="'功能用例统计'">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="测试结果" name="first">
<el-tab-pane v-if="resultEnable" label="测试结果" name="first">
<functional-result :function-result="report.functionResult"/>
</el-tab-pane>
<el-tab-pane label="失败用例" name="second">
<el-tab-pane v-if="failureEnable" label="失败用例" name="second">
<functional-failure-result :share-id="shareId" :is-share="isShare" :is-template="isTemplate" :report="report" :plan-id="planId"/>
</el-tab-pane>
<el-tab-pane label="缺陷列表" name="third">
<el-tab-pane v-if="issueEnable" label="缺陷列表" name="third">
<functional-issue-list :share-id="shareId" :is-share="isShare" :is-template="isTemplate" :report="report" :plan-id="planId"/>
</el-tab-pane>
<!-- <el-tab-pane label="所有用例" name="fourth">所有用例</el-tab-pane>-->
@ -33,9 +33,46 @@ export default {
};
},
props: [
'report','planId', 'isTemplate', 'isShare', 'shareId'
'report','planId', 'isTemplate', 'isShare', 'shareId', 'config'
],
computed: {
resultEnable() {
let disable = this.report.config && this.report.config.functional.children.result.enable === false;
return !disable;
},
failureEnable() {
let disable = this.report.config && this.report.config.functional.children.failure.enable === false;
return !disable;
},
issueEnable() {
let disable = this.report.config && this.report.config.functional.children.issue.enable === false;
return !disable;
},
},
mounted() {
this.initActiveName();
},
watch: {
resultEnable() {
this.initActiveName();
},
failureEnable() {
this.initActiveName();
},
issueEnable() {
this.initActiveName();
},
},
methods: {
initActiveName() {
if (this.resultEnable) {
this.activeName = 'first';
} else if (this.failureEnable) {
this.activeName = 'second';
} else if (this.issueEnable) {
this.activeName = 'third';
}
},
handleClick(tab, event) {
}
}

View File

@ -1,10 +1,10 @@
<template>
<test-plan-report-container :title="'性能用例数'">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="测试结果" name="first">
<el-tab-pane v-if="resultEnable" label="测试结果" name="first">
<load-result :load-result="report.loadResult"/>
</el-tab-pane>
<el-tab-pane label="失败用例" name="second">
<el-tab-pane v-if="failureEnable" label="失败用例" name="second">
<load-failure-result :share-id="shareId" :is-share="isShare" :is-template="isTemplate" :report="report" :plan-id="planId"/>
</el-tab-pane>
<!-- <el-tab-pane label="所有用例" name="fourth">所有用例</el-tab-pane>-->
@ -38,7 +38,35 @@ export default {
'isShare',
'shareId'
],
computed: {
resultEnable() {
let disable = this.report.config && this.report.config.load.children.result.enable === false;
return !disable;
},
failureEnable() {
let disable = this.report.config && this.report.config.load.children.failure.enable === false;
return !disable;
},
},
watch: {
resultEnable() {
this.initActiveName();
},
failureEnable() {
this.initActiveName();
},
},
mounted() {
this.initActiveName();
},
methods: {
initActiveName() {
if (this.resultEnable) {
this.activeName = 'first';
} else if (this.failureEnable) {
this.activeName = 'second';
}
},
handleClick(tab, event) {
}
}

View File

@ -25,7 +25,7 @@
{{'配置'}}
</el-button>
</el-row>
<test-plan-report-edit ref="reportEdit"/>
<test-plan-report-edit :plan-id="planId" :config.sync="report.config" ref="reportEdit"/>
</div>
</template>
@ -50,7 +50,7 @@ export default {
return {
result: {},
isTestManagerOrTestUser: true,
shareUrl: ''
shareUrl: '',
};
},
methods: {

View File

@ -4,13 +4,11 @@
<el-card v-loading="result ? result.loading : false">
<test-plan-report-buttons :plan-id="planId" :is-share="isShare" :report="report"
v-if="!isTemplate && !isShare"/>
<test-plan-overview-report :report="report"/>
<test-plan-summary-report :is-template="isTemplate" :is-share="isShare" :report="report" :plan-id="planId"/>
<test-plan-functional-report :share-id="shareId" :is-share="isShare" :is-template="isTemplate" v-if="functionalEnable" :plan-id="planId" :report="report"/>
<test-plan-api-report :share-id="shareId" :is-share="isShare" :is-template="isTemplate" v-if="apiEnable" :report="report" :plan-id="planId"/>
<test-plan-load-report :share-id="shareId" :is-share="isShare" :is-template="isTemplate" v-if="loadEnable" :report="report" :plan-id="planId"/>
<test-plan-report-edit ref="reportEdit"/>
<test-plan-overview-report v-if="overviewEnable" :report="report"/>
<test-plan-summary-report v-if="summaryEnable" :is-template="isTemplate" :is-share="isShare" :report="report" :plan-id="planId"/>
<test-plan-functional-report v-if="functionalEnable" :share-id="shareId" :is-share="isShare" :is-template="isTemplate" :plan-id="planId" :report="report"/>
<test-plan-api-report v-if="apiEnable" :share-id="shareId" :is-share="isShare" :is-template="isTemplate" :report="report" :plan-id="planId"/>
<test-plan-load-report v-if="loadEnable" :share-id="shareId" :is-share="isShare" :is-template="isTemplate" :report="report" :plan-id="planId"/>
</el-card>
</el-main>
</div>
@ -38,7 +36,7 @@ export default {
TestPlanLoadReport,
TestPlanApiReport,
TestPlanFunctionalReport,
},
},
props: {
planId:String,
isTemplate: Boolean,
@ -61,30 +59,105 @@ export default {
this.getReport();
},
computed: {
overviewEnable() {
let disable = this.report.config && this.report.config.overview.enable === false;
return !disable;
},
summaryEnable() {
let disable = this.report.config && this.report.config.summary.enable === false;
return !disable;
},
functionalEnable() {
return this.report.functionResult && this.report.functionResult.caseData.length > 0;
let disable = this.report.config && this.report.config.functional.enable === false;
return !disable && this.report.functionResult && this.report.functionResult.caseData.length > 0 ;
},
apiEnable() {
return this.report.apiResult && (this.report.apiResult.apiCaseData.length > 0 || this.report.apiResult.apiScenarioData.length) > 0;
let disable = this.report.config && this.report.config.api.enable === false;
return !disable && this.report.apiResult && (this.report.apiResult.apiCaseData.length > 0 || this.report.apiResult.apiScenarioData.length) > 0;
},
loadEnable() {
return this.report.loadResult && this.report.loadResult.caseData.length > 0;
let disable = this.report.config && this.report.config.load.enable === false;
return !disable && this.report.loadResult && this.report.loadResult.caseData.length > 0;
}
},
methods: {
getReport() {
if (this.isTemplate) {
this.report = "#report";
this.report.config = this.getDefaultConfig(this.report.config);
} else if (this.isShare) {
this.result = getShareTestPlanReport(this.shareId, this.planId, (data) => {
this.report = data;
this.report.config = this.getDefaultConfig(this.report.config);
});
} else {
this.result = getTestPlanReport(this.planId, (data) => {
this.report = data;
this.report.config = this.getDefaultConfig(this.report.config);
});
}
},
getDefaultConfig(configStr) {
if (configStr) {
return JSON.parse(configStr);
}
return {
overview: {
enable: true,
name: '概览'
},
summary: {
enable: true,
name: '报告总结'
},
functional: {
enable: true,
name: '功能用例统计分析',
children: {
result: {
enable: true,
name: '测试结果',
},
failure: {
enable: true,
name: '失败用例',
},
issue: {
enable: true,
name: '缺陷列表',
}
}
},
api: {
enable: true,
name: '接口用例统计分析',
children: {
result: {
enable: true,
name: '测试结果',
},
failure: {
enable: true,
name: '失败用例',
},
}
},
load: {
enable: true,
name: '性能用例统计分析',
children: {
result: {
enable: true,
name: '测试结果',
},
failure: {
enable: true,
name: '失败用例',
},
}
}
};
}
}
}
</script>

View File

@ -33,7 +33,7 @@ export default {
planId: String,
report: Object,
isTemplate: Boolean,
isShare: Boolean
isShare: Boolean,
},
data() {
return {

View File

@ -0,0 +1,39 @@
<template>
<span v-if="config">
<div v-for="(item, index) in config" :key="index">
<el-checkbox v-model="item.enable" @change="click(item)">{{ item.name }}
<test-plan-report-config :config="item.children"/>
</el-checkbox>
</div>
</span>
</template>
<script>
export default {
name: "TestPlanReportConfig",
props: ['config'],
methods: {
click(item) {
if (item.children) {
for (const childName in item.children) {
let child = item.children[childName];
if (child) {
child.enable = item.enable;
this.click(child);
}
}
}
},
}
}
</script>
<style scoped>
.el-checkbox {
margin: 10px;
}
.el-checkbox >>> .el-checkbox__input {
vertical-align: top;
}
</style>

View File

@ -1,52 +1,65 @@
<template>
<ms-edit-dialog
width="60%"
width="600px"
:visible.sync="visible"
@confirm="confirm"
:title="'模板配置'"
append-to-body
ref="msEditDialog">
<el-scrollbar>
<div class="config">
<test-plan-report-config :config="editConfig"/>
</div>
</el-scrollbar>
</ms-edit-dialog>
</template>
<script>
import MsEditDialog from "@/business/components/common/components/MsEditDialog";
import {getCurrentProjectID} from "@/common/js/utils";
import TestPlanReportConfig
from "@/business/components/track/plan/view/comonents/report/detail/component/TestPlanReportConfig";
import {editPlanReportConfig} from "@/network/test-plan";
export default {
name: "TestPlanReportEdit",
components: {MsEditDialog},
components: {TestPlanReportConfig, MsEditDialog},
data() {
return {
visible: false,
config: {
api: {
}
}
editConfig: {}
}
},
computed: {
projectId() {
return getCurrentProjectID();
props: {
planId: String,
config: {
type: [Object, String],
}
},
props: ['caseId', 'planId'],
methods: {
open(data) {
open() {
this.visible = true;
this.editConfig = JSON.parse(JSON.stringify(this.config));
},
handleClose() {
this.visible = false;
},
confirm() {
this.$refs.issueEditDetail.save();
let param = {
id: this.planId,
reportConfig: JSON.stringify(this.config)
};
editPlanReportConfig(param, () => {
this.$emit('update:config', JSON.parse(JSON.stringify(this.editConfig)));
});
this.$emit('refresh');
this.visible = false;
}
}
};
</script>
<style scoped>
.config {
margin-left: 200px;
}
</style>

View File

@ -1,7 +1,7 @@
import {post, get} from "@/common/js/ajax";
import {success} from "@/common/js/message";
import i18n from "@/i18n/i18n";
import {baseGet} from "@/network/base-network";
import {baseGet, basePost} from "@/network/base-network";
export function getTestPlanReport(planId, callback) {
if (planId) {
@ -29,6 +29,15 @@ export function editPlanReport(param) {
});
}
export function editPlanReportConfig(param, callback) {
return basePost('/test/plan/edit/report/config', param, (data) => {
success(i18n.t('commons.save_success'));
if (callback) {
callback(data);
}
});
}
export function getPlanFunctionFailureCase(planId, callback) {
return planId ? baseGet('/test/plan/case/list/failure/' + planId, callback) : {};
}