feat(测试计划): 补充生成报告功能

This commit is contained in:
song-cc-rock 2024-05-16 16:44:14 +08:00 committed by Craftsman
parent 0d60b03697
commit f0afd5241c
24 changed files with 780 additions and 346 deletions

View File

@ -1,12 +1,16 @@
package io.metersphere.plan.domain;
import io.metersphere.validation.groups.*;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import lombok.Data;
@Data
public class TestPlanConfig implements Serializable {
@ -23,23 +27,22 @@ public class TestPlanConfig implements Serializable {
@NotNull(message = "{test_plan_config.repeat_case.not_blank}", groups = {Created.class})
private Boolean repeatCase;
@Schema(description = "测试计划通过阈值;0-100", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "是否开启测试规划", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{test_plan_config.test_planning.not_blank}", groups = {Created.class})
private Boolean testPlanning;
@Schema(title = "测试计划通过阈值; 0-100, 保留两位小数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{test_plan_config.pass_threshold.not_blank}", groups = {Created.class})
private Double passThreshold;
@Schema(description = "是否开启测试规划", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan_config.test_planning.not_blank}", groups = {Created.class})
@Size(min = 1, max = 1, message = "{test_plan_config.test_planning.length_range}", groups = {Created.class, Updated.class})
private Boolean testPlanning;
private static final long serialVersionUID = 1L;
public enum Column {
testPlanId("test_plan_id", "testPlanId", "VARCHAR", false),
automaticStatusUpdate("automatic_status_update", "automaticStatusUpdate", "BIT", false),
repeatCase("repeat_case", "repeatCase", "BIT", false),
passThreshold("pass_threshold", "passThreshold", "DOUBLE", false),
testPlanning("test_planning", "testPlanning", "BIT", false);
testPlanning("test_planning", "testPlanning", "BIT", false),
passThreshold("pass_threshold", "passThreshold", "DECIMAL", false);
private static final String BEGINNING_DELIMITER = "`";

View File

@ -294,66 +294,6 @@ public class TestPlanConfigExample {
return (Criteria) this;
}
public Criteria andPassThresholdIsNull() {
addCriterion("pass_threshold is null");
return (Criteria) this;
}
public Criteria andPassThresholdIsNotNull() {
addCriterion("pass_threshold is not null");
return (Criteria) this;
}
public Criteria andPassThresholdEqualTo(Double value) {
addCriterion("pass_threshold =", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotEqualTo(Double value) {
addCriterion("pass_threshold <>", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdGreaterThan(Double value) {
addCriterion("pass_threshold >", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdGreaterThanOrEqualTo(Double value) {
addCriterion("pass_threshold >=", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLessThan(Double value) {
addCriterion("pass_threshold <", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLessThanOrEqualTo(Double value) {
addCriterion("pass_threshold <=", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdIn(List<Double> values) {
addCriterion("pass_threshold in", values, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotIn(List<Double> values) {
addCriterion("pass_threshold not in", values, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdBetween(Double value1, Double value2) {
addCriterion("pass_threshold between", value1, value2, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotBetween(Double value1, Double value2) {
addCriterion("pass_threshold not between", value1, value2, "passThreshold");
return (Criteria) this;
}
public Criteria andTestPlanningIsNull() {
addCriterion("test_planning is null");
return (Criteria) this;
@ -413,6 +353,66 @@ public class TestPlanConfigExample {
addCriterion("test_planning not between", value1, value2, "testPlanning");
return (Criteria) this;
}
public Criteria andPassThresholdIsNull() {
addCriterion("pass_threshold is null");
return (Criteria) this;
}
public Criteria andPassThresholdIsNotNull() {
addCriterion("pass_threshold is not null");
return (Criteria) this;
}
public Criteria andPassThresholdEqualTo(Long value) {
addCriterion("pass_threshold =", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotEqualTo(Long value) {
addCriterion("pass_threshold <>", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdGreaterThan(Long value) {
addCriterion("pass_threshold >", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdGreaterThanOrEqualTo(Long value) {
addCriterion("pass_threshold >=", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLessThan(Long value) {
addCriterion("pass_threshold <", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLessThanOrEqualTo(Long value) {
addCriterion("pass_threshold <=", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdIn(List<Long> values) {
addCriterion("pass_threshold in", values, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotIn(List<Long> values) {
addCriterion("pass_threshold not in", values, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdBetween(Long value1, Long value2) {
addCriterion("pass_threshold between", value1, value2, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotBetween(Long value1, Long value2) {
addCriterion("pass_threshold not between", value1, value2, "passThreshold");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -1,12 +1,16 @@
package io.metersphere.plan.domain;
import io.metersphere.validation.groups.*;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import lombok.Data;
@Data
public class TestPlanReport implements Serializable {
@ -31,30 +35,36 @@ public class TestPlanReport implements Serializable {
@Schema(description = "创建时间")
private Long createTime;
@Schema(description = "开始时间")
@Schema(description = "执行时间;计划真正执行的时间")
private Long executeTime;
@Schema(description = "开始时间;计划开始执行的时间")
private Long startTime;
@Schema(description = "结束时间")
@Schema(description = "结束时间;计划执行结束的时间")
private Long endTime;
@Schema(description = "触发类型")
private String triggerMode;
@Schema(description = "执行状态: 未执行, 执行中, 已停止, 已完成;", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "执行状态", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan_report.exec_status.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{test_plan_report.exec_status.length_range}", groups = {Created.class, Updated.class})
private String execStatus;
@Schema(description = "结果状态: 成功, 失败, 阻塞, 误报")
@Schema(description = "结果状态", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan_report.result_status.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{test_plan_report.result_status.length_range}", groups = {Created.class, Updated.class})
private String resultStatus;
@Schema(description = "通过阈值", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan_report.pass_threshold.not_blank}", groups = {Created.class})
@Size(min = 1, max = 100, message = "{test_plan_report.pass_threshold.length_range}", groups = {Created.class, Updated.class})
private String passThreshold;
@Schema(description = "通过率")
private Long passRate;
private Double passRate;
@Schema(description = "触发类型", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan_report.trigger_mode.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{test_plan_report.trigger_mode.length_range}", groups = {Created.class, Updated.class})
private String triggerMode;
@Schema(description = "通过阈值", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{test_plan_report.pass_threshold.not_blank}", groups = {Created.class})
private Double passThreshold;
@Schema(description = "项目id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan_report.project_id.not_blank}", groups = {Created.class})
@ -77,13 +87,14 @@ public class TestPlanReport implements Serializable {
name("name", "name", "VARCHAR", true),
createUser("create_user", "createUser", "VARCHAR", false),
createTime("create_time", "createTime", "BIGINT", false),
executeTime("execute_time", "executeTime", "BIGINT", false),
startTime("start_time", "startTime", "BIGINT", false),
endTime("end_time", "endTime", "BIGINT", false),
triggerMode("trigger_mode", "triggerMode", "VARCHAR", false),
execStatus("exec_status", "execStatus", "VARCHAR", false),
resultStatus("result_status", "resultStatus", "VARCHAR", false),
passThreshold("pass_threshold", "passThreshold", "VARCHAR", false),
passRate("pass_rate", "passRate", "DECIMAL", false),
triggerMode("trigger_mode", "triggerMode", "VARCHAR", false),
passThreshold("pass_threshold", "passThreshold", "DECIMAL", false),
projectId("project_id", "projectId", "VARCHAR", false),
integrated("integrated", "integrated", "BIT", false),
deleted("deleted", "deleted", "BIT", false);

View File

@ -444,6 +444,66 @@ public class TestPlanReportExample {
return (Criteria) this;
}
public Criteria andExecuteTimeIsNull() {
addCriterion("execute_time is null");
return (Criteria) this;
}
public Criteria andExecuteTimeIsNotNull() {
addCriterion("execute_time is not null");
return (Criteria) this;
}
public Criteria andExecuteTimeEqualTo(Long value) {
addCriterion("execute_time =", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeNotEqualTo(Long value) {
addCriterion("execute_time <>", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeGreaterThan(Long value) {
addCriterion("execute_time >", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeGreaterThanOrEqualTo(Long value) {
addCriterion("execute_time >=", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeLessThan(Long value) {
addCriterion("execute_time <", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeLessThanOrEqualTo(Long value) {
addCriterion("execute_time <=", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeIn(List<Long> values) {
addCriterion("execute_time in", values, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeNotIn(List<Long> values) {
addCriterion("execute_time not in", values, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeBetween(Long value1, Long value2) {
addCriterion("execute_time between", value1, value2, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeNotBetween(Long value1, Long value2) {
addCriterion("execute_time not between", value1, value2, "executeTime");
return (Criteria) this;
}
public Criteria andStartTimeIsNull() {
addCriterion("start_time is null");
return (Criteria) this;
@ -564,76 +624,6 @@ public class TestPlanReportExample {
return (Criteria) this;
}
public Criteria andTriggerModeIsNull() {
addCriterion("trigger_mode is null");
return (Criteria) this;
}
public Criteria andTriggerModeIsNotNull() {
addCriterion("trigger_mode is not null");
return (Criteria) this;
}
public Criteria andTriggerModeEqualTo(String value) {
addCriterion("trigger_mode =", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeNotEqualTo(String value) {
addCriterion("trigger_mode <>", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeGreaterThan(String value) {
addCriterion("trigger_mode >", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeGreaterThanOrEqualTo(String value) {
addCriterion("trigger_mode >=", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeLessThan(String value) {
addCriterion("trigger_mode <", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeLessThanOrEqualTo(String value) {
addCriterion("trigger_mode <=", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeLike(String value) {
addCriterion("trigger_mode like", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeNotLike(String value) {
addCriterion("trigger_mode not like", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeIn(List<String> values) {
addCriterion("trigger_mode in", values, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeNotIn(List<String> values) {
addCriterion("trigger_mode not in", values, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeBetween(String value1, String value2) {
addCriterion("trigger_mode between", value1, value2, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeNotBetween(String value1, String value2) {
addCriterion("trigger_mode not between", value1, value2, "triggerMode");
return (Criteria) this;
}
public Criteria andExecStatusIsNull() {
addCriterion("exec_status is null");
return (Criteria) this;
@ -774,76 +764,6 @@ public class TestPlanReportExample {
return (Criteria) this;
}
public Criteria andPassThresholdIsNull() {
addCriterion("pass_threshold is null");
return (Criteria) this;
}
public Criteria andPassThresholdIsNotNull() {
addCriterion("pass_threshold is not null");
return (Criteria) this;
}
public Criteria andPassThresholdEqualTo(String value) {
addCriterion("pass_threshold =", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotEqualTo(String value) {
addCriterion("pass_threshold <>", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdGreaterThan(String value) {
addCriterion("pass_threshold >", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdGreaterThanOrEqualTo(String value) {
addCriterion("pass_threshold >=", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLessThan(String value) {
addCriterion("pass_threshold <", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLessThanOrEqualTo(String value) {
addCriterion("pass_threshold <=", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLike(String value) {
addCriterion("pass_threshold like", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotLike(String value) {
addCriterion("pass_threshold not like", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdIn(List<String> values) {
addCriterion("pass_threshold in", values, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotIn(List<String> values) {
addCriterion("pass_threshold not in", values, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdBetween(String value1, String value2) {
addCriterion("pass_threshold between", value1, value2, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotBetween(String value1, String value2) {
addCriterion("pass_threshold not between", value1, value2, "passThreshold");
return (Criteria) this;
}
public Criteria andPassRateIsNull() {
addCriterion("pass_rate is null");
return (Criteria) this;
@ -904,6 +824,136 @@ public class TestPlanReportExample {
return (Criteria) this;
}
public Criteria andTriggerModeIsNull() {
addCriterion("trigger_mode is null");
return (Criteria) this;
}
public Criteria andTriggerModeIsNotNull() {
addCriterion("trigger_mode is not null");
return (Criteria) this;
}
public Criteria andTriggerModeEqualTo(String value) {
addCriterion("trigger_mode =", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeNotEqualTo(String value) {
addCriterion("trigger_mode <>", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeGreaterThan(String value) {
addCriterion("trigger_mode >", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeGreaterThanOrEqualTo(String value) {
addCriterion("trigger_mode >=", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeLessThan(String value) {
addCriterion("trigger_mode <", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeLessThanOrEqualTo(String value) {
addCriterion("trigger_mode <=", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeLike(String value) {
addCriterion("trigger_mode like", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeNotLike(String value) {
addCriterion("trigger_mode not like", value, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeIn(List<String> values) {
addCriterion("trigger_mode in", values, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeNotIn(List<String> values) {
addCriterion("trigger_mode not in", values, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeBetween(String value1, String value2) {
addCriterion("trigger_mode between", value1, value2, "triggerMode");
return (Criteria) this;
}
public Criteria andTriggerModeNotBetween(String value1, String value2) {
addCriterion("trigger_mode not between", value1, value2, "triggerMode");
return (Criteria) this;
}
public Criteria andPassThresholdIsNull() {
addCriterion("pass_threshold is null");
return (Criteria) this;
}
public Criteria andPassThresholdIsNotNull() {
addCriterion("pass_threshold is not null");
return (Criteria) this;
}
public Criteria andPassThresholdEqualTo(Long value) {
addCriterion("pass_threshold =", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotEqualTo(Long value) {
addCriterion("pass_threshold <>", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdGreaterThan(Long value) {
addCriterion("pass_threshold >", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdGreaterThanOrEqualTo(Long value) {
addCriterion("pass_threshold >=", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLessThan(Long value) {
addCriterion("pass_threshold <", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdLessThanOrEqualTo(Long value) {
addCriterion("pass_threshold <=", value, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdIn(List<Long> values) {
addCriterion("pass_threshold in", values, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotIn(List<Long> values) {
addCriterion("pass_threshold not in", values, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdBetween(Long value1, Long value2) {
addCriterion("pass_threshold between", value1, value2, "passThreshold");
return (Criteria) this;
}
public Criteria andPassThresholdNotBetween(Long value1, Long value2) {
addCriterion("pass_threshold not between", value1, value2, "passThreshold");
return (Criteria) this;
}
public Criteria andProjectIdIsNull() {
addCriterion("project_id is null");
return (Criteria) this;

View File

@ -5,8 +5,8 @@
<id column="test_plan_id" jdbcType="VARCHAR" property="testPlanId" />
<result column="automatic_status_update" jdbcType="BIT" property="automaticStatusUpdate" />
<result column="repeat_case" jdbcType="BIT" property="repeatCase" />
<result column="pass_threshold" jdbcType="DOUBLE" property="passThreshold" />
<result column="test_planning" jdbcType="BIT" property="testPlanning" />
<result column="pass_threshold" jdbcType="DECIMAL" property="passThreshold" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -67,7 +67,7 @@
</where>
</sql>
<sql id="Base_Column_List">
test_plan_id, automatic_status_update, repeat_case, pass_threshold, test_planning
test_plan_id, automatic_status_update, repeat_case, test_planning, pass_threshold
</sql>
<select id="selectByExample" parameterType="io.metersphere.plan.domain.TestPlanConfigExample" resultMap="BaseResultMap">
select
@ -101,9 +101,9 @@
</delete>
<insert id="insert" parameterType="io.metersphere.plan.domain.TestPlanConfig">
insert into test_plan_config (test_plan_id, automatic_status_update, repeat_case,
pass_threshold, test_planning)
test_planning, pass_threshold)
values (#{testPlanId,jdbcType=VARCHAR}, #{automaticStatusUpdate,jdbcType=BIT}, #{repeatCase,jdbcType=BIT},
#{passThreshold,jdbcType=DOUBLE}, #{testPlanning,jdbcType=BIT})
#{testPlanning,jdbcType=BIT}, #{passThreshold,jdbcType=DECIMAL})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.plan.domain.TestPlanConfig">
insert into test_plan_config
@ -117,12 +117,12 @@
<if test="repeatCase != null">
repeat_case,
</if>
<if test="passThreshold != null">
pass_threshold,
</if>
<if test="testPlanning != null">
test_planning,
</if>
<if test="passThreshold != null">
pass_threshold,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="testPlanId != null">
@ -134,12 +134,12 @@
<if test="repeatCase != null">
#{repeatCase,jdbcType=BIT},
</if>
<if test="passThreshold != null">
#{passThreshold,jdbcType=DOUBLE},
</if>
<if test="testPlanning != null">
#{testPlanning,jdbcType=BIT},
</if>
<if test="passThreshold != null">
#{passThreshold,jdbcType=DECIMAL},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.plan.domain.TestPlanConfigExample" resultType="java.lang.Long">
@ -160,12 +160,12 @@
<if test="record.repeatCase != null">
repeat_case = #{record.repeatCase,jdbcType=BIT},
</if>
<if test="record.passThreshold != null">
pass_threshold = #{record.passThreshold,jdbcType=DOUBLE},
</if>
<if test="record.testPlanning != null">
test_planning = #{record.testPlanning,jdbcType=BIT},
</if>
<if test="record.passThreshold != null">
pass_threshold = #{record.passThreshold,jdbcType=DECIMAL},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -176,8 +176,8 @@
set test_plan_id = #{record.testPlanId,jdbcType=VARCHAR},
automatic_status_update = #{record.automaticStatusUpdate,jdbcType=BIT},
repeat_case = #{record.repeatCase,jdbcType=BIT},
pass_threshold = #{record.passThreshold,jdbcType=DOUBLE},
test_planning = #{record.testPlanning,jdbcType=BIT}
test_planning = #{record.testPlanning,jdbcType=BIT},
pass_threshold = #{record.passThreshold,jdbcType=DECIMAL}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -191,12 +191,12 @@
<if test="repeatCase != null">
repeat_case = #{repeatCase,jdbcType=BIT},
</if>
<if test="passThreshold != null">
pass_threshold = #{passThreshold,jdbcType=DOUBLE},
</if>
<if test="testPlanning != null">
test_planning = #{testPlanning,jdbcType=BIT},
</if>
<if test="passThreshold != null">
pass_threshold = #{passThreshold,jdbcType=DECIMAL},
</if>
</set>
where test_plan_id = #{testPlanId,jdbcType=VARCHAR}
</update>
@ -204,18 +204,18 @@
update test_plan_config
set automatic_status_update = #{automaticStatusUpdate,jdbcType=BIT},
repeat_case = #{repeatCase,jdbcType=BIT},
pass_threshold = #{passThreshold,jdbcType=DOUBLE},
test_planning = #{testPlanning,jdbcType=BIT}
test_planning = #{testPlanning,jdbcType=BIT},
pass_threshold = #{passThreshold,jdbcType=DECIMAL}
where test_plan_id = #{testPlanId,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
insert into test_plan_config
(test_plan_id, automatic_status_update, repeat_case, pass_threshold, test_planning
(test_plan_id, automatic_status_update, repeat_case, test_planning, pass_threshold
)
values
<foreach collection="list" item="item" separator=",">
(#{item.testPlanId,jdbcType=VARCHAR}, #{item.automaticStatusUpdate,jdbcType=BIT},
#{item.repeatCase,jdbcType=BIT}, #{item.passThreshold,jdbcType=DOUBLE}, #{item.testPlanning,jdbcType=BIT}
#{item.repeatCase,jdbcType=BIT}, #{item.testPlanning,jdbcType=BIT}, #{item.passThreshold,jdbcType=DECIMAL}
)
</foreach>
</insert>
@ -238,12 +238,12 @@
<if test="'repeat_case'.toString() == column.value">
#{item.repeatCase,jdbcType=BIT}
</if>
<if test="'pass_threshold'.toString() == column.value">
#{item.passThreshold,jdbcType=DOUBLE}
</if>
<if test="'test_planning'.toString() == column.value">
#{item.testPlanning,jdbcType=BIT}
</if>
<if test="'pass_threshold'.toString() == column.value">
#{item.passThreshold,jdbcType=DECIMAL}
</if>
</foreach>
)
</foreach>

View File

@ -7,13 +7,14 @@
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="create_user" jdbcType="VARCHAR" property="createUser" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="execute_time" jdbcType="BIGINT" property="executeTime" />
<result column="start_time" jdbcType="BIGINT" property="startTime" />
<result column="end_time" jdbcType="BIGINT" property="endTime" />
<result column="trigger_mode" jdbcType="VARCHAR" property="triggerMode" />
<result column="exec_status" jdbcType="VARCHAR" property="execStatus" />
<result column="result_status" jdbcType="VARCHAR" property="resultStatus" />
<result column="pass_threshold" jdbcType="VARCHAR" property="passThreshold" />
<result column="pass_rate" jdbcType="DECIMAL" property="passRate" />
<result column="trigger_mode" jdbcType="VARCHAR" property="triggerMode" />
<result column="pass_threshold" jdbcType="DECIMAL" property="passThreshold" />
<result column="project_id" jdbcType="VARCHAR" property="projectId" />
<result column="integrated" jdbcType="BIT" property="integrated" />
<result column="deleted" jdbcType="BIT" property="deleted" />
@ -77,8 +78,9 @@
</where>
</sql>
<sql id="Base_Column_List">
id, test_plan_id, `name`, create_user, create_time, start_time, end_time, trigger_mode,
exec_status, result_status, pass_threshold, pass_rate, project_id, integrated, deleted
id, test_plan_id, `name`, create_user, create_time, execute_time, start_time, end_time,
exec_status, result_status, pass_rate, trigger_mode, pass_threshold, project_id,
integrated, deleted
</sql>
<select id="selectByExample" parameterType="io.metersphere.plan.domain.TestPlanReportExample" resultMap="BaseResultMap">
select
@ -112,17 +114,17 @@
</delete>
<insert id="insert" parameterType="io.metersphere.plan.domain.TestPlanReport">
insert into test_plan_report (id, test_plan_id, `name`,
create_user, create_time, start_time,
end_time, trigger_mode, exec_status,
result_status, pass_threshold, pass_rate,
project_id, integrated, deleted
)
create_user, create_time, execute_time,
start_time, end_time, exec_status,
result_status, pass_rate, trigger_mode,
pass_threshold, project_id, integrated,
deleted)
values (#{id,jdbcType=VARCHAR}, #{testPlanId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{createUser,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{startTime,jdbcType=BIGINT},
#{endTime,jdbcType=BIGINT}, #{triggerMode,jdbcType=VARCHAR}, #{execStatus,jdbcType=VARCHAR},
#{resultStatus,jdbcType=VARCHAR}, #{passThreshold,jdbcType=VARCHAR}, #{passRate,jdbcType=DECIMAL},
#{projectId,jdbcType=VARCHAR}, #{integrated,jdbcType=BIT}, #{deleted,jdbcType=BIT}
)
#{createUser,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{executeTime,jdbcType=BIGINT},
#{startTime,jdbcType=BIGINT}, #{endTime,jdbcType=BIGINT}, #{execStatus,jdbcType=VARCHAR},
#{resultStatus,jdbcType=VARCHAR}, #{passRate,jdbcType=DECIMAL}, #{triggerMode,jdbcType=VARCHAR},
#{passThreshold,jdbcType=DECIMAL}, #{projectId,jdbcType=VARCHAR}, #{integrated,jdbcType=BIT},
#{deleted,jdbcType=BIT})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.plan.domain.TestPlanReport">
insert into test_plan_report
@ -142,27 +144,30 @@
<if test="createTime != null">
create_time,
</if>
<if test="executeTime != null">
execute_time,
</if>
<if test="startTime != null">
start_time,
</if>
<if test="endTime != null">
end_time,
</if>
<if test="triggerMode != null">
trigger_mode,
</if>
<if test="execStatus != null">
exec_status,
</if>
<if test="resultStatus != null">
result_status,
</if>
<if test="passThreshold != null">
pass_threshold,
</if>
<if test="passRate != null">
pass_rate,
</if>
<if test="triggerMode != null">
trigger_mode,
</if>
<if test="passThreshold != null">
pass_threshold,
</if>
<if test="projectId != null">
project_id,
</if>
@ -189,27 +194,30 @@
<if test="createTime != null">
#{createTime,jdbcType=BIGINT},
</if>
<if test="executeTime != null">
#{executeTime,jdbcType=BIGINT},
</if>
<if test="startTime != null">
#{startTime,jdbcType=BIGINT},
</if>
<if test="endTime != null">
#{endTime,jdbcType=BIGINT},
</if>
<if test="triggerMode != null">
#{triggerMode,jdbcType=VARCHAR},
</if>
<if test="execStatus != null">
#{execStatus,jdbcType=VARCHAR},
</if>
<if test="resultStatus != null">
#{resultStatus,jdbcType=VARCHAR},
</if>
<if test="passThreshold != null">
#{passThreshold,jdbcType=VARCHAR},
</if>
<if test="passRate != null">
#{passRate,jdbcType=DECIMAL},
</if>
<if test="triggerMode != null">
#{triggerMode,jdbcType=VARCHAR},
</if>
<if test="passThreshold != null">
#{passThreshold,jdbcType=DECIMAL},
</if>
<if test="projectId != null">
#{projectId,jdbcType=VARCHAR},
</if>
@ -245,27 +253,30 @@
<if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT},
</if>
<if test="record.executeTime != null">
execute_time = #{record.executeTime,jdbcType=BIGINT},
</if>
<if test="record.startTime != null">
start_time = #{record.startTime,jdbcType=BIGINT},
</if>
<if test="record.endTime != null">
end_time = #{record.endTime,jdbcType=BIGINT},
</if>
<if test="record.triggerMode != null">
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
</if>
<if test="record.execStatus != null">
exec_status = #{record.execStatus,jdbcType=VARCHAR},
</if>
<if test="record.resultStatus != null">
result_status = #{record.resultStatus,jdbcType=VARCHAR},
</if>
<if test="record.passThreshold != null">
pass_threshold = #{record.passThreshold,jdbcType=VARCHAR},
</if>
<if test="record.passRate != null">
pass_rate = #{record.passRate,jdbcType=DECIMAL},
</if>
<if test="record.triggerMode != null">
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
</if>
<if test="record.passThreshold != null">
pass_threshold = #{record.passThreshold,jdbcType=DECIMAL},
</if>
<if test="record.projectId != null">
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
@ -287,13 +298,14 @@
`name` = #{record.name,jdbcType=VARCHAR},
create_user = #{record.createUser,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
execute_time = #{record.executeTime,jdbcType=BIGINT},
start_time = #{record.startTime,jdbcType=BIGINT},
end_time = #{record.endTime,jdbcType=BIGINT},
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
exec_status = #{record.execStatus,jdbcType=VARCHAR},
result_status = #{record.resultStatus,jdbcType=VARCHAR},
pass_threshold = #{record.passThreshold,jdbcType=VARCHAR},
pass_rate = #{record.passRate,jdbcType=DECIMAL},
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
pass_threshold = #{record.passThreshold,jdbcType=DECIMAL},
project_id = #{record.projectId,jdbcType=VARCHAR},
integrated = #{record.integrated,jdbcType=BIT},
deleted = #{record.deleted,jdbcType=BIT}
@ -316,27 +328,30 @@
<if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT},
</if>
<if test="executeTime != null">
execute_time = #{executeTime,jdbcType=BIGINT},
</if>
<if test="startTime != null">
start_time = #{startTime,jdbcType=BIGINT},
</if>
<if test="endTime != null">
end_time = #{endTime,jdbcType=BIGINT},
</if>
<if test="triggerMode != null">
trigger_mode = #{triggerMode,jdbcType=VARCHAR},
</if>
<if test="execStatus != null">
exec_status = #{execStatus,jdbcType=VARCHAR},
</if>
<if test="resultStatus != null">
result_status = #{resultStatus,jdbcType=VARCHAR},
</if>
<if test="passThreshold != null">
pass_threshold = #{passThreshold,jdbcType=VARCHAR},
</if>
<if test="passRate != null">
pass_rate = #{passRate,jdbcType=DECIMAL},
</if>
<if test="triggerMode != null">
trigger_mode = #{triggerMode,jdbcType=VARCHAR},
</if>
<if test="passThreshold != null">
pass_threshold = #{passThreshold,jdbcType=DECIMAL},
</if>
<if test="projectId != null">
project_id = #{projectId,jdbcType=VARCHAR},
</if>
@ -355,13 +370,14 @@
`name` = #{name,jdbcType=VARCHAR},
create_user = #{createUser,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
execute_time = #{executeTime,jdbcType=BIGINT},
start_time = #{startTime,jdbcType=BIGINT},
end_time = #{endTime,jdbcType=BIGINT},
trigger_mode = #{triggerMode,jdbcType=VARCHAR},
exec_status = #{execStatus,jdbcType=VARCHAR},
result_status = #{resultStatus,jdbcType=VARCHAR},
pass_threshold = #{passThreshold,jdbcType=VARCHAR},
pass_rate = #{passRate,jdbcType=DECIMAL},
trigger_mode = #{triggerMode,jdbcType=VARCHAR},
pass_threshold = #{passThreshold,jdbcType=DECIMAL},
project_id = #{projectId,jdbcType=VARCHAR},
integrated = #{integrated,jdbcType=BIT},
deleted = #{deleted,jdbcType=BIT}
@ -369,16 +385,16 @@
</update>
<insert id="batchInsert" parameterType="map">
insert into test_plan_report
(id, test_plan_id, `name`, create_user, create_time, start_time, end_time, trigger_mode,
exec_status, result_status, pass_threshold, pass_rate, project_id, integrated,
deleted)
(id, test_plan_id, `name`, create_user, create_time, execute_time, start_time, end_time,
exec_status, result_status, pass_rate, trigger_mode, pass_threshold, project_id,
integrated, deleted)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.testPlanId,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR},
#{item.createUser,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT}, #{item.startTime,jdbcType=BIGINT},
#{item.endTime,jdbcType=BIGINT}, #{item.triggerMode,jdbcType=VARCHAR}, #{item.execStatus,jdbcType=VARCHAR},
#{item.resultStatus,jdbcType=VARCHAR}, #{item.passThreshold,jdbcType=VARCHAR},
#{item.passRate,jdbcType=DECIMAL}, #{item.projectId,jdbcType=VARCHAR}, #{item.integrated,jdbcType=BIT},
#{item.createUser,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT}, #{item.executeTime,jdbcType=BIGINT},
#{item.startTime,jdbcType=BIGINT}, #{item.endTime,jdbcType=BIGINT}, #{item.execStatus,jdbcType=VARCHAR},
#{item.resultStatus,jdbcType=VARCHAR}, #{item.passRate,jdbcType=DECIMAL}, #{item.triggerMode,jdbcType=VARCHAR},
#{item.passThreshold,jdbcType=DECIMAL}, #{item.projectId,jdbcType=VARCHAR}, #{item.integrated,jdbcType=BIT},
#{item.deleted,jdbcType=BIT})
</foreach>
</insert>
@ -407,27 +423,30 @@
<if test="'create_time'.toString() == column.value">
#{item.createTime,jdbcType=BIGINT}
</if>
<if test="'execute_time'.toString() == column.value">
#{item.executeTime,jdbcType=BIGINT}
</if>
<if test="'start_time'.toString() == column.value">
#{item.startTime,jdbcType=BIGINT}
</if>
<if test="'end_time'.toString() == column.value">
#{item.endTime,jdbcType=BIGINT}
</if>
<if test="'trigger_mode'.toString() == column.value">
#{item.triggerMode,jdbcType=VARCHAR}
</if>
<if test="'exec_status'.toString() == column.value">
#{item.execStatus,jdbcType=VARCHAR}
</if>
<if test="'result_status'.toString() == column.value">
#{item.resultStatus,jdbcType=VARCHAR}
</if>
<if test="'pass_threshold'.toString() == column.value">
#{item.passThreshold,jdbcType=VARCHAR}
</if>
<if test="'pass_rate'.toString() == column.value">
#{item.passRate,jdbcType=DECIMAL}
</if>
<if test="'trigger_mode'.toString() == column.value">
#{item.triggerMode,jdbcType=VARCHAR}
</if>
<if test="'pass_threshold'.toString() == column.value">
#{item.passThreshold,jdbcType=DECIMAL}
</if>
<if test="'project_id'.toString() == column.value">
#{item.projectId,jdbcType=VARCHAR}
</if>

View File

@ -70,13 +70,14 @@ CREATE TABLE IF NOT EXISTS test_plan_report(
`name` VARCHAR(255) NOT NULL COMMENT '报告名称' ,
`create_user` VARCHAR(50) NOT NULL COMMENT '创建人' ,
`create_time` BIGINT NOT NULL COMMENT '创建时间' ,
`start_time` BIGINT COMMENT '开始时间' ,
`end_time` BIGINT COMMENT '结束时间' ,
`trigger_mode` VARCHAR(50) COMMENT '触发类型' ,
`exec_status` VARCHAR(50) NOT NULL DEFAULT 'PENDING' COMMENT '执行状态: 未执行, 执行中, 已停止, 已完成;' ,
`result_status` VARCHAR(50) DEFAULT '-' COMMENT '结果状态: 成功, 失败, 阻塞, 误报' ,
`pass_threshold` VARCHAR(100) NOT NULL COMMENT '通过阈值' ,
`pass_rate` DECIMAL COMMENT '通过率' ,
`execute_time` BIGINT COMMENT '执行时间;计划真正执行的时间' ,
`start_time` BIGINT COMMENT '开始时间;计划开始执行的时间' ,
`end_time` BIGINT COMMENT '结束时间;计划结束执行的时间' ,
`exec_status` VARCHAR(50) NOT NULL DEFAULT 'PENDING' COMMENT '执行状态' ,
`result_status` VARCHAR(50) NOT NULL DEFAULT '-' COMMENT '结果状态' ,
`pass_rate` DECIMAL(10, 4) COMMENT '通过率' ,
`trigger_mode` VARCHAR(50) NOT NULL COMMENT '触发类型' ,
`pass_threshold` DECIMAL(10, 2) NOT NULL COMMENT '通过阈值' ,
`project_id` VARCHAR(50) NOT NULL COMMENT '项目id' ,
`integrated` BIT NOT NULL DEFAULT 0 COMMENT '是否是集成报告' ,
`deleted` BIT NOT NULL DEFAULT 0 COMMENT '是否删除' ,
@ -117,20 +118,23 @@ CREATE TABLE IF NOT EXISTS test_plan_report_function_case(
PRIMARY KEY (id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '测试计划报告内容功能用例部分';
CREATE UNIQUE INDEX idx_test_plan_report_id ON test_plan_report_function_case(test_plan_report_id);
CREATE INDEX idx_test_plan_report_id ON test_plan_report_function_case(test_plan_report_id);
CREATE TABLE IF NOT EXISTS test_plan_report_bug(
`id` VARCHAR(50) COMMENT 'ID' ,
`test_plan_report_id` VARCHAR(50) COMMENT '报告ID' ,
`bug_id` VARCHAR(50) COMMENT '缺陷ID'
`id` VARCHAR(50) NOT NULL COMMENT 'ID' ,
`test_plan_report_id` VARCHAR(50) NOT NULL COMMENT '测试计划报告ID' ,
`bug_id` VARCHAR(50) NOT NULL COMMENT '缺陷ID' ,
PRIMARY KEY (id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '测试计划报告内容缺陷部分';
CREATE UNIQUE INDEX idx_test_plan_report_id ON test_plan_report_bug(test_plan_report_id);
CREATE INDEX idx_test_plan_report_id ON test_plan_report_bug(test_plan_report_id);
-- 场景步骤 csv 表增加场景ID字段
ALTER TABLE api_scenario_csv_step ADD scenario_id varchar(50) NOT NULL COMMENT '场景ID';
CREATE INDEX idx_scenario_id USING BTREE ON api_scenario_csv_step (scenario_id);
ALTER TABLE test_plan_config MODIFY pass_threshold DECIMAL(10, 2) NOT NULL;
-- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT;

View File

@ -3,7 +3,7 @@ package io.metersphere.sdk.constants;
/**
* 报告执行状态
*/
public enum ReportExecStatus {
public enum ExecStatus {
/**
* 未执行
*/

View File

@ -90,3 +90,4 @@ test_plan.batch.log={0}测试计划
test_plan_report_not_exist=测试计划报告不存在
test_plan_report_id.not_blank=测试计划报告id不能为空
test_plan_report_name.not_blank=测试计划报告名称不能为空
test_plan_not_exist=测试计划不存在

View File

@ -102,3 +102,4 @@ test_plan_report_not_exist=The test plan report does not exist
test_plan_report_id.not_blank=The test plan report id cannot be empty
test_plan_report_name.not_blank=The test plan report name cannot be empty
run_functional_case=Run functional case
test_plan_not_exist=The test plan does not exist

View File

@ -102,3 +102,4 @@ test_plan_report_not_exist=测试计划报告不存在
test_plan_report_id.not_blank=测试计划报告id不能为空
test_plan_report_name.not_blank=测试计划报告名称不能为空
run_functional_case=执行功能用例
test_plan_not_exist=测试计划不存在

View File

@ -102,3 +102,4 @@ test_plan_report_not_exist=測試計劃報告不存在
test_plan_report_id.not_blank=測試計劃報告id不能爲空
test_plan_report_name.not_blank=測試計劃報告名稱不能爲空
run_functional_case=執行功能用例
test_plan_not_exist=測試計劃不存在

View File

@ -83,4 +83,11 @@ public interface ExtBugRelateCaseMapper {
List<CaseRelateBugDTO> getBugCountByIds(@Param("ids") List<String> ids);
List<BugProviderDTO> getTestPlanAssociateBugs(@Param("request") AssociateBugPageRequest request, @Param("sort") String sort);
/**
* 获取计划关联的缺陷ID集合(去重)
* @param planId 计划ID
* @return 缺陷ID集合
*/
List<String> getPlanRelateBugIds(@Param("id") String planId);
}

View File

@ -309,4 +309,8 @@
</if>
</where>
</select>
<select id="getPlanRelateBugIds" resultType="java.lang.String">
select distinct bug_id from bug_relation_case where test_plan_id = #{id}
</select>
</mapper>

View File

@ -3,7 +3,9 @@ package io.metersphere.plan.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.plan.constants.TestPlanResourceConfig;
import io.metersphere.plan.domain.TestPlanReport;
import io.metersphere.plan.dto.request.TestPlanReportBatchRequest;
import io.metersphere.plan.dto.request.TestPlanReportGenRequest;
import io.metersphere.plan.dto.request.TestPlanReportPageRequest;
import io.metersphere.plan.dto.response.TestPlanReportPageResponse;
import io.metersphere.plan.service.TestPlanManagementService;
@ -77,4 +79,12 @@ public class TestPlanReportController {
public void batchDelete(@Validated @RequestBody TestPlanReportBatchRequest request) {
testPlanReportService.batchDelete(request, SessionUtils.getUserId());
}
@PostMapping("/gen")
@Operation(summary = "测试计划-详情-生成报告")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_EXECUTE)
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
public TestPlanReport genReportByManual(@Validated @RequestBody TestPlanReportGenRequest request) {
return testPlanReportService.genReportByManual(request, SessionUtils.getUserId());
}
}

View File

@ -0,0 +1,32 @@
package io.metersphere.plan.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class TestPlanReportGenPreParam {
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "计划ID")
private String testPlanId;
@Schema(description = "计划名称")
private String testPlanName;
@Schema(description = "计划开始时间")
private Long startTime;
@Schema(description = "触发方式")
private String triggerMode;
@Schema(description = "执行状态")
private String execStatus;
@Schema(description = "结果状态")
private String resultStatus;
@Schema(description = "是否集成报告")
private Boolean integrated;
}

View File

@ -0,0 +1,26 @@
package io.metersphere.plan.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class TestPlanReportPostParam {
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "计划ID")
private String testPlanId;
@Schema(description = "报告ID")
private String reportId;
@Schema(description = "计划开始执行时间")
private Long executeTime;
@Schema(description = "计划结束时间")
private Long endTime;
@Schema(description = "执行状态")
private String execStatus;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.plan.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class TestPlanReportGenRequest {
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan.project_id.not_blank}")
private String projectId;
@Schema(description = "计划ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan.id.not_blank}")
private String testPlanId;
}

View File

@ -27,6 +27,13 @@ public interface ExtTestPlanReportMapper {
*/
List<String> getReportBatchIdsByParam(@Param("request") TestPlanReportBatchRequest request);
/**
* 统计报告中执行通过的功能用例数量
* @param reportId 报告ID
* @return 用例数量
*/
Long countExecuteSuccessFunctionalCase(@Param("id") String reportId);
List<TestPlanReport> selectReportByIds(@Param("ids") List<String> ids);
List<ApiReportMessageDTO> getNoticeList(@Param("ids") List<String> subList);

View File

@ -15,6 +15,12 @@
join test_plan tp on tpr.test_plan_id = tp.id
<include refid="queryWhereCondition"/>
</select>
<select id="countExecuteSuccessFunctionalCase" resultType="java.lang.Long">
select count(*) from test_plan_report_function_case tprfc
where tprfc.test_plan_report_id = #{id} and tprfc.execute_result = 'SUCCESS'
</select>
<select id="selectReportByIds" resultType="io.metersphere.plan.domain.TestPlanReport">
select * from test_plan_report where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">

View File

@ -1,23 +1,39 @@
package io.metersphere.plan.service;
import io.metersphere.plan.domain.TestPlanReport;
import io.metersphere.plan.domain.TestPlanReportExample;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.bug.mapper.ExtBugRelateCaseMapper;
import io.metersphere.plan.domain.*;
import io.metersphere.plan.dto.TestPlanReportGenPreParam;
import io.metersphere.plan.dto.TestPlanReportPostParam;
import io.metersphere.plan.dto.request.TestPlanReportBatchRequest;
import io.metersphere.plan.dto.request.TestPlanReportGenRequest;
import io.metersphere.plan.dto.request.TestPlanReportPageRequest;
import io.metersphere.plan.dto.response.TestPlanReportPageResponse;
import io.metersphere.plan.mapper.ExtTestPlanReportMapper;
import io.metersphere.plan.mapper.TestPlanReportMapper;
import io.metersphere.plan.mapper.*;
import io.metersphere.sdk.constants.ExecStatus;
import io.metersphere.sdk.constants.ReportStatus;
import io.metersphere.sdk.constants.TaskTriggerMode;
import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.DateUtils;
import io.metersphere.sdk.util.SubListUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.User;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.service.UserService;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.stereotype.Service;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -25,18 +41,32 @@ import java.util.Map;
@Service
public class TestPlanReportService {
@Resource
private UserMapper userMapper;
@Resource
private UserService userService;
@Resource
private SqlSessionFactory sqlSessionFactory;
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private TestPlanConfigMapper testPlanConfigMapper;
@Resource
private TestPlanReportMapper testPlanReportMapper;
@Resource
private ExtBugRelateCaseMapper extBugRelateCaseMapper;
@Resource
private BugRelationCaseMapper bugRelationCaseMapper;
@Resource
private ExtTestPlanReportMapper extTestPlanReportMapper;
@Resource
private TestPlanReportLogService testPlanReportLogService;
@Resource
private TestPlanReportNoticeService testPlanReportNoticeService;
@Resource
private UserMapper userMapper;
private TestPlanReportSummaryMapper testPlanReportSummaryMapper;
@Resource
private TestPlanFunctionalCaseMapper testPlanFunctionalCaseMapper;
/**
* 分页查询报告列表
@ -95,6 +125,155 @@ public class TestPlanReportService {
}
}
/**
* 手动生成报告
* @param request 请求参数
* @return 报告
*/
public TestPlanReport genReportByManual(TestPlanReportGenRequest request, String currentUser) {
TestPlan testPlan = checkPlan(request.getTestPlanId());
/*
* 手动生成报告
* 1. 构建预生成报告参数
* 2. 预生成报告
* 3. 报告后置处理
*/
TestPlanReportGenPreParam genPreParam = new TestPlanReportGenPreParam();
BeanUtils.copyBean(genPreParam, request);
genPreParam.setTestPlanName(testPlan.getName());
genPreParam.setStartTime(System.currentTimeMillis());
// 手动触发
genPreParam.setTriggerMode(TaskTriggerMode.MANUAL.name());
// 报告预生成时, 执行状态为未执行, 结果状态为'-'
genPreParam.setExecStatus(ExecStatus.PENDING.name());
genPreParam.setResultStatus("-");
// 是否集成报告, 目前根据是否计划组来区分
genPreParam.setIntegrated(StringUtils.equals(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP));
TestPlanReport preReport = preGenReport(genPreParam, currentUser);
TestPlanReportPostParam postParam = new TestPlanReportPostParam();
BeanUtils.copyBean(postParam, request);
postParam.setReportId(preReport.getId());
return postHandleReport(postParam);
}
/**
* 预生成报告内容(后续拆分优化)
* @return 报告
*/
public TestPlanReport preGenReport(TestPlanReportGenPreParam genParam, String currentUser) {
// 准备计划数据
TestPlanConfig testPlanConfig = testPlanConfigMapper.selectByPrimaryKey(genParam.getTestPlanId());
/*
* 预生成报告(后续执行生成报告复用)
* 1. 生成报告用例数据, 缺陷数据
* 2. 生成或计算报告统计数据
*/
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
String reportId = IDGenerator.nextStr();
// 功能用例
List<TestPlanReportFunctionCase> reportFunctionCases = new ArrayList<>();
TestPlanFunctionalCaseExample functionalCaseExample = new TestPlanFunctionalCaseExample();
functionalCaseExample.createCriteria().andTestPlanIdEqualTo(genParam.getTestPlanId());
List<TestPlanFunctionalCase> testPlanFunctionalCases = testPlanFunctionalCaseMapper.selectByExample(functionalCaseExample);
testPlanFunctionalCases.forEach(functionalCase -> {
TestPlanReportFunctionCase reportFunctionCase = new TestPlanReportFunctionCase();
reportFunctionCase.setId(IDGenerator.nextStr());
reportFunctionCase.setTestPlanReportId(reportId);
reportFunctionCase.setFunctionCaseId(functionalCase.getFunctionalCaseId());
reportFunctionCase.setTestPlanFunctionCaseId(functionalCase.getId());
reportFunctionCase.setExecuteResult(functionalCase.getLastExecResult());
reportFunctionCases.add(reportFunctionCase);
});
if (CollectionUtils.isNotEmpty(reportFunctionCases)) {
// 插入计划功能用例关联数据 -> 报告内容
TestPlanReportFunctionCaseMapper batchMapper = sqlSession.getMapper(TestPlanReportFunctionCaseMapper.class);
batchMapper.batchInsert(reportFunctionCases);
}
// TODO: 接口用例, 场景报告内容 (与接口报告是否能一致)
// 计划报告缺陷内容
List<TestPlanReportBug> reportBugs = new ArrayList<>();
List<String> bugIds = extBugRelateCaseMapper.getPlanRelateBugIds(genParam.getTestPlanId());
bugIds.forEach(bugId -> {
TestPlanReportBug reportBug = new TestPlanReportBug();
reportBug.setId(IDGenerator.nextStr());
reportBug.setTestPlanReportId(reportId);
reportBug.setBugId(bugId);
reportBugs.add(reportBug);
});
if (CollectionUtils.isNotEmpty(reportBugs)) {
// 插入计划关联用例缺陷数据(去重) -> 报告内容
TestPlanReportBugMapper batchMapper = sqlSession.getMapper(TestPlanReportBugMapper.class);
batchMapper.batchInsert(reportBugs);
}
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
// 插入报告统计内容
TestPlanReportSummary reportSummary = new TestPlanReportSummary();
reportSummary.setId(IDGenerator.nextStr());
reportSummary.setTestPlanReportId(reportId);
reportSummary.setFunctionalCaseCount((long) (CollectionUtils.isEmpty(reportFunctionCases) ? 0 : reportFunctionCases.size()));
reportSummary.setApiCaseCount(0L);
reportSummary.setApiScenarioCount(0L);
reportSummary.setBugCount((long) (CollectionUtils.isEmpty(reportBugs) ? 0 : reportBugs.size()));
testPlanReportSummaryMapper.insertSelective(reportSummary);
// 插入报告
TestPlanReport report = new TestPlanReport();
BeanUtils.copyBean(report, genParam);
report.setId(reportId);
report.setName(genParam.getTestPlanName() + "-" + DateUtils.getTimeStr(System.currentTimeMillis()));
report.setCreateUser(currentUser);
report.setCreateTime(System.currentTimeMillis());
report.setDeleted(false);
report.setPassThreshold(testPlanConfig.getPassThreshold());
testPlanReportMapper.insertSelective(report);
return report;
}
/**
* 报告结果后置处理
* @param postParam 后置处理参数
* @return 报告
*/
public TestPlanReport postHandleReport(TestPlanReportPostParam postParam) {
/*
* 处理报告(执行状态, 结束时间)
*/
TestPlanReport planReport = checkReport(postParam.getReportId());
BeanUtils.copyBean(planReport, postParam);
/*
* TODO: 计算报告通过率, 并对比阈值生成报告结果状态(目前只有功能用例参与计算)
*/
TestPlanReportSummaryExample example = new TestPlanReportSummaryExample();
example.createCriteria().andTestPlanReportIdEqualTo(postParam.getReportId());
TestPlanReportSummary reportSummary = testPlanReportSummaryMapper.selectByExample(example).get(0);
DecimalFormat rateFormat = new DecimalFormat("#0.0000");
rateFormat.setMinimumFractionDigits(2);
rateFormat.setMaximumFractionDigits(2);
// 通过的功能用例数
// TODO: 接口用例, 场景用例
long functionalCasePassCount = extTestPlanReportMapper.countExecuteSuccessFunctionalCase(postParam.getReportId());
// 用例总数
long caseTotal = reportSummary.getFunctionalCaseCount() + reportSummary.getApiCaseCount() + reportSummary.getApiScenarioCount();
// 通过率 {通过用例数/总用例数}
double passRate = (functionalCasePassCount == 0 || caseTotal == 0) ? 0.00 :
Double.parseDouble(rateFormat.format((double) functionalCasePassCount / (double) caseTotal));
// FIXME: 后续替换成PASS_COUNT {保留该逻辑, 四舍五入导致的边界值数据展示偏差}
if (passRate == 0 && functionalCasePassCount > 0) {
passRate = 0.0001;
} else if (passRate == 100 && functionalCasePassCount < caseTotal) {
passRate = 0.9999;
}
planReport.setPassRate(passRate);
// 计划的(执行)结果状态: 通过率 >= 阈值 ? 成功 : 失败
planReport.setResultStatus(passRate >= planReport.getPassThreshold() ? ReportStatus.SUCCESS.name() : ReportStatus.ERROR.name());
return planReport;
}
/**
* 通过请求参数获取批量操作的ID集合
@ -114,6 +293,20 @@ public class TestPlanReportService {
}
}
/**
* 校验计划是否存在
* @param planId 计划ID
* @return 测试计划
*/
private TestPlan checkPlan(String planId) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
if (testPlan == null) {
throw new MSException(Translator.get("test_plan_not_exist"));
}
return testPlan;
}
/**
* 校验报告是否存在
*

View File

@ -32,6 +32,7 @@ public class TestPlanReportControllerTests extends BaseTest {
private static final String RENAME_PLAN_REPORT = "/test-plan/report/rename";
private static final String DELETE_PLAN_REPORT = "/test-plan/report/delete";
private static final String BATCH_DELETE_PLAN_REPORT = "/test-plan/report/batch-delete";
private static final String GEN_PLAN_REPORT = "/test-plan/report/gen";
private static final String GEN_AND_SHARE = "/test-plan/report/share/gen";
private static final String GET_SHARE_INFO = "/test-plan/report/share/get";
private static final String GET_SHARE_TIME = "/test-plan/report/share/get-share-time";
@ -161,4 +162,25 @@ public class TestPlanReportControllerTests extends BaseTest {
request.setExcludeIds(null);
this.requestPostWithOk(BATCH_DELETE_PLAN_REPORT, request);
}
@Test
@Order(10)
@Sql(scripts = {"/dml/init_test_plan_report_gen.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
void testGenReportError() throws Exception {
TestPlanReportGenRequest genRequest = new TestPlanReportGenRequest();
genRequest.setProjectId("100001100001");
genRequest.setTestPlanId("plan_id_for_gen_report-x");
this.requestPost(GEN_PLAN_REPORT, genRequest, status().is5xxServerError());
}
@Test
@Order(11)
void testGenReportSuccess() throws Exception {
TestPlanReportGenRequest genRequest = new TestPlanReportGenRequest();
genRequest.setProjectId("100001100001");
genRequest.setTestPlanId("plan_id_for_gen_report_1");
this.requestPost(GEN_PLAN_REPORT, genRequest);
genRequest.setTestPlanId("plan_id_for_gen_report");
this.requestPost(GEN_PLAN_REPORT, genRequest);
}
}

View File

@ -643,7 +643,7 @@ public class TestPlanTests extends BaseTest {
request.setGroupId(testPlanTestService.selectTestPlanByName("testPlan_60").getGroupId());
this.requestPost(URL_POST_TEST_PLAN_ADD, request);
request.setGroupId(TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID);
request.setPassThreshold(100.111);
request.setPassThreshold(100.11);
this.requestPost(URL_POST_TEST_PLAN_ADD, request).andExpect(status().isBadRequest());
request.setPassThreshold(-0.12);
this.requestPost(URL_POST_TEST_PLAN_ADD, request).andExpect(status().isBadRequest());
@ -889,7 +889,7 @@ public class TestPlanTests extends BaseTest {
updateRequest = testPlanTestService.generateUpdateRequest(testPlan.getId());
updateRequest.setAutomaticStatusUpdate(true);
updateRequest.setRepeatCase(true);
updateRequest.setPassThreshold(43.123);
updateRequest.setPassThreshold(43.12);
mvcResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_UPDATE, updateRequest);
returnStr = mvcResult.getResponse().getContentAsString();
holder = JSON.parseObject(returnStr, ResultHolder.class);
@ -900,7 +900,7 @@ public class TestPlanTests extends BaseTest {
updateRequest = testPlanTestService.generateUpdateRequest(testPlan.getId());
updateRequest.setAutomaticStatusUpdate(false);
updateRequest.setRepeatCase(false);
updateRequest.setPassThreshold(56.478);
updateRequest.setPassThreshold(56.47);
mvcResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_UPDATE, updateRequest);
returnStr = mvcResult.getResponse().getContentAsString();
holder = JSON.parseObject(returnStr, ResultHolder.class);

View File

@ -0,0 +1,19 @@
-- 计划的测试数据
INSERT INTO `test_plan`(`id`, `num`, `project_id`, `group_id`, `module_id`, `name`, `status`, `type`, `tags`, `create_time`, `create_user`, `update_time`, `update_user`, `planned_start_time`, `planned_end_time`, `actual_start_time`, `actual_end_time`, `description`) VALUES
('plan_id_for_gen_report', 100001, '100001100001', 'NONE', '1', 'gen-report-plan', 'PREPARED', 'TEST_PLAN', NULL, CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '11'),
('plan_id_for_gen_report_1', 100001, '100001100001', 'NONE', '1', 'gen-report-plan-1', 'PREPARED', 'TEST_PLAN', NULL, CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '11');
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`, `test_planning`) VALUES
('plan_id_for_gen_report', b'0', b'0', 100.00, b'0'),
('plan_id_for_gen_report_1', b'0', b'0', 0.00, b'0');
-- 计划关联用例执行的测试数据
INSERT INTO `test_plan_functional_case` (`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`) VALUES
('plan_id_for_gen_report_case_1', 'plan_id_for_gen_report', 'f1', CURRENT_TIMESTAMP, 'admin', 'admin', CURRENT_TIMESTAMP, 'PENDING', 1),
('plan_id_for_gen_report_case_2', 'plan_id_for_gen_report', 'f2', CURRENT_TIMESTAMP, 'admin', 'admin', CURRENT_TIMESTAMP, 'SUCCESS', 2),
('plan_id_for_gen_report_case_3', 'plan_id_for_gen_report', 'f3', CURRENT_TIMESTAMP, 'admin', 'admin', CURRENT_TIMESTAMP, 'ERROR', 3),
('plan_id_for_gen_report_case_4', 'plan_id_for_gen_report', 'f4', CURRENT_TIMESTAMP, 'admin', 'admin', CURRENT_TIMESTAMP, 'BLOCKED', 4),
('plan_id_for_gen_report_case_5', 'plan_id_for_gen_report', 'f5', CURRENT_TIMESTAMP, 'admin', 'admin', CURRENT_TIMESTAMP, 'FAKE_ERROR', 5);
-- 计划关联缺陷的测试数据
INSERT INTO `bug_relation_case`(`id`, `case_id`, `bug_id`, `case_type`, `test_plan_id`, `test_plan_case_id`, `create_user`, `create_time`, `update_time`) VALUES
('test-plan-bug-relate-case-1', 'f1', 'test-plan-bug-id', 'FUNCTIONAL', 'plan_id_for_gen_report', 'f1', 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);