feat(缺陷管理): 补充缺陷管理列表接口

This commit is contained in:
song-cc-rock 2024-01-10 17:38:37 +08:00
parent 72383aa6c3
commit 7ee95417d7
67 changed files with 1821 additions and 403 deletions

View File

@ -68,7 +68,7 @@ public class Bug implements Serializable {
private String status;
@Schema(description = "标签")
private String tag;
private java.util.List<String> tags;
@Schema(description = "第三方平台缺陷ID")
private String platformBugId;
@ -99,7 +99,7 @@ public class Bug implements Serializable {
templateId("template_id", "templateId", "VARCHAR", false),
platform("platform", "platform", "VARCHAR", false),
status("status", "status", "VARCHAR", true),
tag("tag", "tag", "VARCHAR", false),
tags("tags", "tags", "VARCHAR", false),
platformBugId("platform_bug_id", "platformBugId", "VARCHAR", false),
deleteUser("delete_user", "deleteUser", "VARCHAR", false),
deleteTime("delete_time", "deleteTime", "BIGINT", false),

View File

@ -64,19 +64,50 @@ public class BugExample {
}
protected abstract static class GeneratedCriteria {
protected List<Criterion> tagsCriteria;
protected List<Criterion> allCriteria;
protected List<Criterion> criteria;
protected GeneratedCriteria() {
super();
criteria = new ArrayList<Criterion>();
tagsCriteria = new ArrayList<Criterion>();
}
public List<Criterion> getTagsCriteria() {
return tagsCriteria;
}
protected void addTagsCriterion(String condition, Object value, String property) {
if (value == null) {
throw new RuntimeException("Value for " + property + " cannot be null");
}
tagsCriteria.add(new Criterion(condition, value, "io.metersphere.handler.ListTypeHandler"));
allCriteria = null;
}
protected void addTagsCriterion(String condition, List<String> value1, List<String> value2, String property) {
if (value1 == null || value2 == null) {
throw new RuntimeException("Between values for " + property + " cannot be null");
}
tagsCriteria.add(new Criterion(condition, value1, value2, "io.metersphere.handler.ListTypeHandler"));
allCriteria = null;
}
public boolean isValid() {
return criteria.size() > 0;
return criteria.size() > 0
|| tagsCriteria.size() > 0;
}
public List<Criterion> getAllCriteria() {
return criteria;
if (allCriteria == null) {
allCriteria = new ArrayList<Criterion>();
allCriteria.addAll(criteria);
allCriteria.addAll(tagsCriteria);
}
return allCriteria;
}
public List<Criterion> getCriteria() {
@ -88,6 +119,7 @@ public class BugExample {
throw new RuntimeException("Value for condition cannot be null");
}
criteria.add(new Criterion(condition));
allCriteria = null;
}
protected void addCriterion(String condition, Object value, String property) {
@ -95,6 +127,7 @@ public class BugExample {
throw new RuntimeException("Value for " + property + " cannot be null");
}
criteria.add(new Criterion(condition, value));
allCriteria = null;
}
protected void addCriterion(String condition, Object value1, Object value2, String property) {
@ -102,6 +135,7 @@ public class BugExample {
throw new RuntimeException("Between values for " + property + " cannot be null");
}
criteria.add(new Criterion(condition, value1, value2));
allCriteria = null;
}
public Criteria andIdIsNull() {
@ -984,73 +1018,73 @@ public class BugExample {
return (Criteria) this;
}
public Criteria andTagIsNull() {
addCriterion("tag is null");
public Criteria andTagsIsNull() {
addCriterion("tags is null");
return (Criteria) this;
}
public Criteria andTagIsNotNull() {
addCriterion("tag is not null");
public Criteria andTagsIsNotNull() {
addCriterion("tags is not null");
return (Criteria) this;
}
public Criteria andTagEqualTo(String value) {
addCriterion("tag =", value, "tag");
public Criteria andTagsEqualTo(List<String> value) {
addTagsCriterion("tags =", value, "tags");
return (Criteria) this;
}
public Criteria andTagNotEqualTo(String value) {
addCriterion("tag <>", value, "tag");
public Criteria andTagsNotEqualTo(List<String> value) {
addTagsCriterion("tags <>", value, "tags");
return (Criteria) this;
}
public Criteria andTagGreaterThan(String value) {
addCriterion("tag >", value, "tag");
public Criteria andTagsGreaterThan(List<String> value) {
addTagsCriterion("tags >", value, "tags");
return (Criteria) this;
}
public Criteria andTagGreaterThanOrEqualTo(String value) {
addCriterion("tag >=", value, "tag");
public Criteria andTagsGreaterThanOrEqualTo(List<String> value) {
addTagsCriterion("tags >=", value, "tags");
return (Criteria) this;
}
public Criteria andTagLessThan(String value) {
addCriterion("tag <", value, "tag");
public Criteria andTagsLessThan(List<String> value) {
addTagsCriterion("tags <", value, "tags");
return (Criteria) this;
}
public Criteria andTagLessThanOrEqualTo(String value) {
addCriterion("tag <=", value, "tag");
public Criteria andTagsLessThanOrEqualTo(List<String> value) {
addTagsCriterion("tags <=", value, "tags");
return (Criteria) this;
}
public Criteria andTagLike(String value) {
addCriterion("tag like", value, "tag");
public Criteria andTagsLike(List<String> value) {
addTagsCriterion("tags like", value, "tags");
return (Criteria) this;
}
public Criteria andTagNotLike(String value) {
addCriterion("tag not like", value, "tag");
public Criteria andTagsNotLike(List<String> value) {
addTagsCriterion("tags not like", value, "tags");
return (Criteria) this;
}
public Criteria andTagIn(List<String> values) {
addCriterion("tag in", values, "tag");
public Criteria andTagsIn(List<List<String>> values) {
addTagsCriterion("tags in", values, "tags");
return (Criteria) this;
}
public Criteria andTagNotIn(List<String> values) {
addCriterion("tag not in", values, "tag");
public Criteria andTagsNotIn(List<List<String>> values) {
addTagsCriterion("tags not in", values, "tags");
return (Criteria) this;
}
public Criteria andTagBetween(String value1, String value2) {
addCriterion("tag between", value1, value2, "tag");
public Criteria andTagsBetween(List<String> value1, List<String> value2) {
addTagsCriterion("tags between", value1, value2, "tags");
return (Criteria) this;
}
public Criteria andTagNotBetween(String value1, String value2) {
addCriterion("tag not between", value1, value2, "tag");
public Criteria andTagsNotBetween(List<String> value1, List<String> value2) {
addTagsCriterion("tags not between", value1, value2, "tags");
return (Criteria) this;
}

View File

@ -38,6 +38,11 @@ public class BugLocalAttachment implements Serializable {
@NotNull(message = "{bug_local_attachment.size.not_blank}", groups = {Created.class})
private Long size;
@Schema(description = "文件来源", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{bug_local_attachment.source.not_blank}", groups = {Created.class})
@Size(min = 1, max = 255, message = "{bug_local_attachment.source.length_range}", groups = {Created.class, Updated.class})
private String source;
@Schema(description = "创建人")
private String createUser;
@ -52,6 +57,7 @@ public class BugLocalAttachment implements Serializable {
fileId("file_id", "fileId", "VARCHAR", false),
fileName("file_name", "fileName", "VARCHAR", false),
size("size", "size", "BIGINT", true),
source("source", "source", "VARCHAR", true),
createUser("create_user", "createUser", "VARCHAR", false),
createTime("create_time", "createTime", "BIGINT", false);

View File

@ -444,6 +444,76 @@ public class BugLocalAttachmentExample {
return (Criteria) this;
}
public Criteria andSourceIsNull() {
addCriterion("`source` is null");
return (Criteria) this;
}
public Criteria andSourceIsNotNull() {
addCriterion("`source` is not null");
return (Criteria) this;
}
public Criteria andSourceEqualTo(String value) {
addCriterion("`source` =", value, "source");
return (Criteria) this;
}
public Criteria andSourceNotEqualTo(String value) {
addCriterion("`source` <>", value, "source");
return (Criteria) this;
}
public Criteria andSourceGreaterThan(String value) {
addCriterion("`source` >", value, "source");
return (Criteria) this;
}
public Criteria andSourceGreaterThanOrEqualTo(String value) {
addCriterion("`source` >=", value, "source");
return (Criteria) this;
}
public Criteria andSourceLessThan(String value) {
addCriterion("`source` <", value, "source");
return (Criteria) this;
}
public Criteria andSourceLessThanOrEqualTo(String value) {
addCriterion("`source` <=", value, "source");
return (Criteria) this;
}
public Criteria andSourceLike(String value) {
addCriterion("`source` like", value, "source");
return (Criteria) this;
}
public Criteria andSourceNotLike(String value) {
addCriterion("`source` not like", value, "source");
return (Criteria) this;
}
public Criteria andSourceIn(List<String> values) {
addCriterion("`source` in", values, "source");
return (Criteria) this;
}
public Criteria andSourceNotIn(List<String> values) {
addCriterion("`source` not in", values, "source");
return (Criteria) this;
}
public Criteria andSourceBetween(String value1, String value2) {
addCriterion("`source` between", value1, value2, "source");
return (Criteria) this;
}
public Criteria andSourceNotBetween(String value1, String value2) {
addCriterion("`source` not between", value1, value2, "source");
return (Criteria) this;
}
public Criteria andCreateUserIsNull() {
addCriterion("create_user is null");
return (Criteria) this;

View File

@ -7,6 +7,7 @@
<result column="file_id" jdbcType="VARCHAR" property="fileId" />
<result column="file_name" jdbcType="VARCHAR" property="fileName" />
<result column="size" jdbcType="BIGINT" property="size" />
<result column="source" jdbcType="VARCHAR" property="source" />
<result column="create_user" jdbcType="VARCHAR" property="createUser" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
</resultMap>
@ -69,7 +70,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, bug_id, file_id, file_name, `size`, create_user, create_time
id, bug_id, file_id, file_name, `size`, `source`, create_user, create_time
</sql>
<select id="selectByExample" parameterType="io.metersphere.bug.domain.BugLocalAttachmentExample" resultMap="BaseResultMap">
select
@ -103,11 +104,11 @@
</delete>
<insert id="insert" parameterType="io.metersphere.bug.domain.BugLocalAttachment">
insert into bug_local_attachment (id, bug_id, file_id,
file_name, `size`, create_user,
create_time)
file_name, `size`, `source`,
create_user, create_time)
values (#{id,jdbcType=VARCHAR}, #{bugId,jdbcType=VARCHAR}, #{fileId,jdbcType=VARCHAR},
#{fileName,jdbcType=VARCHAR}, #{size,jdbcType=BIGINT}, #{createUser,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT})
#{fileName,jdbcType=VARCHAR}, #{size,jdbcType=BIGINT}, #{source,jdbcType=VARCHAR},
#{createUser,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.bug.domain.BugLocalAttachment">
insert into bug_local_attachment
@ -127,6 +128,9 @@
<if test="size != null">
`size`,
</if>
<if test="source != null">
`source`,
</if>
<if test="createUser != null">
create_user,
</if>
@ -150,6 +154,9 @@
<if test="size != null">
#{size,jdbcType=BIGINT},
</if>
<if test="source != null">
#{source,jdbcType=VARCHAR},
</if>
<if test="createUser != null">
#{createUser,jdbcType=VARCHAR},
</if>
@ -182,6 +189,9 @@
<if test="record.size != null">
`size` = #{record.size,jdbcType=BIGINT},
</if>
<if test="record.source != null">
`source` = #{record.source,jdbcType=VARCHAR},
</if>
<if test="record.createUser != null">
create_user = #{record.createUser,jdbcType=VARCHAR},
</if>
@ -200,6 +210,7 @@
file_id = #{record.fileId,jdbcType=VARCHAR},
file_name = #{record.fileName,jdbcType=VARCHAR},
`size` = #{record.size,jdbcType=BIGINT},
`source` = #{record.source,jdbcType=VARCHAR},
create_user = #{record.createUser,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT}
<if test="_parameter != null">
@ -221,6 +232,9 @@
<if test="size != null">
`size` = #{size,jdbcType=BIGINT},
</if>
<if test="source != null">
`source` = #{source,jdbcType=VARCHAR},
</if>
<if test="createUser != null">
create_user = #{createUser,jdbcType=VARCHAR},
</if>
@ -236,18 +250,19 @@
file_id = #{fileId,jdbcType=VARCHAR},
file_name = #{fileName,jdbcType=VARCHAR},
`size` = #{size,jdbcType=BIGINT},
`source` = #{source,jdbcType=VARCHAR},
create_user = #{createUser,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT}
where id = #{id,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
insert into bug_local_attachment
(id, bug_id, file_id, file_name, `size`, create_user, create_time)
(id, bug_id, file_id, file_name, `size`, `source`, create_user, create_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.bugId,jdbcType=VARCHAR}, #{item.fileId,jdbcType=VARCHAR},
#{item.fileName,jdbcType=VARCHAR}, #{item.size,jdbcType=BIGINT}, #{item.createUser,jdbcType=VARCHAR},
#{item.createTime,jdbcType=BIGINT})
#{item.fileName,jdbcType=VARCHAR}, #{item.size,jdbcType=BIGINT}, #{item.source,jdbcType=VARCHAR},
#{item.createUser,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -275,6 +290,9 @@
<if test="'size'.toString() == column.value">
#{item.size,jdbcType=BIGINT}
</if>
<if test="'source'.toString() == column.value">
#{item.source,jdbcType=VARCHAR}
</if>
<if test="'create_user'.toString() == column.value">
#{item.createUser,jdbcType=VARCHAR}
</if>

View File

@ -15,7 +15,7 @@
<result column="template_id" jdbcType="VARCHAR" property="templateId" />
<result column="platform" jdbcType="VARCHAR" property="platform" />
<result column="status" jdbcType="VARCHAR" property="status" />
<result column="tag" jdbcType="VARCHAR" property="tag" />
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
<result column="platform_bug_id" jdbcType="VARCHAR" property="platformBugId" />
<result column="delete_user" jdbcType="VARCHAR" property="deleteUser" />
<result column="delete_time" jdbcType="BIGINT" property="deleteTime" />
@ -45,6 +45,25 @@
</when>
</choose>
</foreach>
<foreach collection="criteria.tagsCriteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value,typeHandler=io.metersphere.handler.ListTypeHandler}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value,typeHandler=io.metersphere.handler.ListTypeHandler} and #{criterion.secondValue,typeHandler=io.metersphere.handler.ListTypeHandler}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem,typeHandler=io.metersphere.handler.ListTypeHandler}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
@ -74,6 +93,25 @@
</when>
</choose>
</foreach>
<foreach collection="criteria.tagsCriteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value,typeHandler=io.metersphere.handler.ListTypeHandler}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value,typeHandler=io.metersphere.handler.ListTypeHandler} and #{criterion.secondValue,typeHandler=io.metersphere.handler.ListTypeHandler}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem,typeHandler=io.metersphere.handler.ListTypeHandler}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
@ -81,8 +119,8 @@
</sql>
<sql id="Base_Column_List">
id, num, title, handle_users, handle_user, create_user, create_time, update_user,
update_time, project_id, template_id, platform, `status`, tag, platform_bug_id, delete_user,
delete_time, deleted
update_time, project_id, template_id, platform, `status`, tags, platform_bug_id,
delete_user, delete_time, deleted
</sql>
<select id="selectByExample" parameterType="io.metersphere.bug.domain.BugExample" resultMap="BaseResultMap">
select
@ -119,16 +157,16 @@
handle_users, handle_user, create_user,
create_time, update_user, update_time,
project_id, template_id, platform,
`status`, tag, platform_bug_id,
delete_user, delete_time, deleted
)
`status`, tags,
platform_bug_id, delete_user, delete_time,
deleted)
values (#{id,jdbcType=VARCHAR}, #{num,jdbcType=INTEGER}, #{title,jdbcType=VARCHAR},
#{handleUsers,jdbcType=VARCHAR}, #{handleUser,jdbcType=VARCHAR}, #{createUser,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT}, #{updateUser,jdbcType=VARCHAR}, #{updateTime,jdbcType=BIGINT},
#{projectId,jdbcType=VARCHAR}, #{templateId,jdbcType=VARCHAR}, #{platform,jdbcType=VARCHAR},
#{status,jdbcType=VARCHAR}, #{tag,jdbcType=VARCHAR}, #{platformBugId,jdbcType=VARCHAR},
#{deleteUser,jdbcType=VARCHAR}, #{deleteTime,jdbcType=BIGINT}, #{deleted,jdbcType=BIT}
)
#{status,jdbcType=VARCHAR}, #{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
#{platformBugId,jdbcType=VARCHAR}, #{deleteUser,jdbcType=VARCHAR}, #{deleteTime,jdbcType=BIGINT},
#{deleted,jdbcType=BIT})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.bug.domain.Bug">
insert into bug
@ -172,8 +210,8 @@
<if test="status != null">
`status`,
</if>
<if test="tag != null">
tag,
<if test="tags != null">
tags,
</if>
<if test="platformBugId != null">
platform_bug_id,
@ -228,8 +266,8 @@
<if test="status != null">
#{status,jdbcType=VARCHAR},
</if>
<if test="tag != null">
#{tag,jdbcType=VARCHAR},
<if test="tags != null">
#{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
</if>
<if test="platformBugId != null">
#{platformBugId,jdbcType=VARCHAR},
@ -293,8 +331,8 @@
<if test="record.status != null">
`status` = #{record.status,jdbcType=VARCHAR},
</if>
<if test="record.tag != null">
tag = #{record.tag,jdbcType=VARCHAR},
<if test="record.tags != null">
tags = #{record.tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
</if>
<if test="record.platformBugId != null">
platform_bug_id = #{record.platformBugId,jdbcType=VARCHAR},
@ -328,7 +366,7 @@
template_id = #{record.templateId,jdbcType=VARCHAR},
platform = #{record.platform,jdbcType=VARCHAR},
`status` = #{record.status,jdbcType=VARCHAR},
tag = #{record.tag,jdbcType=VARCHAR},
tags = #{record.tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
platform_bug_id = #{record.platformBugId,jdbcType=VARCHAR},
delete_user = #{record.deleteUser,jdbcType=VARCHAR},
delete_time = #{record.deleteTime,jdbcType=BIGINT},
@ -376,8 +414,8 @@
<if test="status != null">
`status` = #{status,jdbcType=VARCHAR},
</if>
<if test="tag != null">
tag = #{tag,jdbcType=VARCHAR},
<if test="tags != null">
tags = #{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
</if>
<if test="platformBugId != null">
platform_bug_id = #{platformBugId,jdbcType=VARCHAR},
@ -408,7 +446,7 @@
template_id = #{templateId,jdbcType=VARCHAR},
platform = #{platform,jdbcType=VARCHAR},
`status` = #{status,jdbcType=VARCHAR},
tag = #{tag,jdbcType=VARCHAR},
tags = #{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
platform_bug_id = #{platformBugId,jdbcType=VARCHAR},
delete_user = #{deleteUser,jdbcType=VARCHAR},
delete_time = #{deleteTime,jdbcType=BIGINT},
@ -418,7 +456,7 @@
<insert id="batchInsert" parameterType="map">
insert into bug
(id, num, title, handle_users, handle_user, create_user, create_time, update_user,
update_time, project_id, template_id, platform, `status`, tag, platform_bug_id,
update_time, project_id, template_id, platform, `status`, tags, platform_bug_id,
delete_user, delete_time, deleted)
values
<foreach collection="list" item="item" separator=",">
@ -426,9 +464,9 @@
#{item.handleUsers,jdbcType=VARCHAR}, #{item.handleUser,jdbcType=VARCHAR}, #{item.createUser,jdbcType=VARCHAR},
#{item.createTime,jdbcType=BIGINT}, #{item.updateUser,jdbcType=VARCHAR}, #{item.updateTime,jdbcType=BIGINT},
#{item.projectId,jdbcType=VARCHAR}, #{item.templateId,jdbcType=VARCHAR}, #{item.platform,jdbcType=VARCHAR},
#{item.status,jdbcType=VARCHAR}, #{item.tag,jdbcType=VARCHAR}, #{item.platformBugId,jdbcType=VARCHAR},
#{item.deleteUser,jdbcType=VARCHAR}, #{item.deleteTime,jdbcType=BIGINT}, #{item.deleted,jdbcType=BIT}
)
#{item.status,jdbcType=VARCHAR}, #{item.tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
#{item.platformBugId,jdbcType=VARCHAR}, #{item.deleteUser,jdbcType=VARCHAR}, #{item.deleteTime,jdbcType=BIGINT},
#{item.deleted,jdbcType=BIT})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -480,8 +518,8 @@
<if test="'status'.toString() == column.value">
#{item.status,jdbcType=VARCHAR}
</if>
<if test="'tag'.toString() == column.value">
#{item.tag,jdbcType=VARCHAR}
<if test="'tags'.toString() == column.value">
#{item.tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler}
</if>
<if test="'platform_bug_id'.toString() == column.value">
#{item.platformBugId,jdbcType=VARCHAR}

View File

@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS bug(
`template_id` VARCHAR(50) NOT NULL COMMENT '模板ID' ,
`platform` VARCHAR(50) NOT NULL COMMENT '缺陷平台' ,
`status` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '状态' ,
`tag` VARCHAR(1000) COMMENT '标签' ,
`tags` VARCHAR(1000) COMMENT '标签' ,
`platform_bug_id` VARCHAR(50) COMMENT '第三方平台缺陷ID' ,
`delete_user` VARCHAR(50) NOT NULL COMMENT '删除人' ,
`delete_time` BIGINT NOT NULL COMMENT '删除时间' ,
@ -54,16 +54,21 @@ CREATE TABLE IF NOT EXISTS bug_follower(
CREATE INDEX idx_follow_id ON bug_follower(user_id);
CREATE TABLE IF NOT EXISTS bug_local_attachment(
`id` VARCHAR(255) NOT NULL COMMENT 'ID' ,
`bug_id` VARCHAR(50) NOT NULL COMMENT '缺陷ID' ,
`file_id` VARCHAR(50) NOT NULL COMMENT '文件ID' ,
`file_name` VARCHAR(255) NOT NULL COMMENT '文件名称' ,
`size` BIGINT NOT NULL COMMENT '文件大小' ,
`create_user` VARCHAR(50) NOT NULL COMMENT '创建人' ,
`create_time` BIGINT NOT NULL COMMENT '创建时间' ,
PRIMARY KEY (id)
`id` VARCHAR(255) NOT NULL COMMENT 'ID' ,
`bug_id` VARCHAR(50) NOT NULL COMMENT '缺陷ID' ,
`file_id` VARCHAR(50) NOT NULL COMMENT '文件ID' ,
`file_name` VARCHAR(255) NOT NULL COMMENT '文件名称' ,
`size` BIGINT NOT NULL COMMENT '文件大小' ,
`source` VARCHAR(255) NOT NULL DEFAULT 'ATTACHMENT' COMMENT '文件来源' ,
`create_user` VARCHAR(50) NOT NULL COMMENT '创建人' ,
`create_time` BIGINT NOT NULL COMMENT '创建时间' ,
PRIMARY KEY (id)
) COMMENT = '缺陷本地附件';
CREATE INDEX idx_bug_id ON bug_local_attachment(bug_id);
CREATE INDEX idx_file_id ON bug_local_attachment(file_id);
CREATE INDEX idx_source ON bug_local_attachment(source);
CREATE TABLE IF NOT EXISTS bug_comment(
`id` VARCHAR(50) NOT NULL COMMENT 'ID' ,
`bug_id` VARCHAR(50) NOT NULL COMMENT '缺陷ID' ,

View File

@ -136,11 +136,10 @@ INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_APPLICATION_WORKSTATION:UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_LOG:READ');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_LOG:UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'BUG:READ');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'BUG:READ+ADD');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'BUG:READ+UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'BUG:READ+DELETE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'BUG:READ+EXPORT');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_BUG:READ');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_BUG:READ+ADD');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_BUG:READ+UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_BUG:READ+DELETE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_BUG:READ+EXPORT');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_BASE_INFO:READ+UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_API_DEBUG:READ');
@ -256,10 +255,10 @@ INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_APPLICATION_WORKSTATION:UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_LOG:READ');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_LOG:UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'BUG:READ');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'BUG:READ+ADD');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'BUG:READ+UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'BUG:READ+DELETE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_BUG:READ');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_BUG:READ+ADD');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_BUG:READ+UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_BUG:READ+DELETE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_BUG:READ+EXPORT');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_BASE_INFO:READ+UPDATE');
INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_API_DEBUG:READ');
@ -342,9 +341,20 @@ INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'functional_priority'), 'P2', 'P2', 1);
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'functional_priority'), 'P3', 'P3', 1);
-- 初始化组织功能用例模板
-- 初始化组织缺陷严重程度
INSERT INTO custom_field(id, name, scene, `type`, remark, internal, scope_type, create_time, update_time, create_user, scope_id) VALUES(UUID_SHORT(), 'bug_degree', 'BUG', 'SELECT', '', 1, 'ORGANIZATION', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', '100001');
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'bug_degree'), UUID_SHORT(), '提示', 1);
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'bug_degree'), UUID_SHORT(), '一般', 1);
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'bug_degree'), UUID_SHORT(), '严重', 1);
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'bug_degree'), UUID_SHORT(), '致命', 1);
-- 初始化组织功能用例默认模板, 缺陷默认模板
INSERT INTO template (id,name,remark,internal,update_time,create_time,create_user,scope_type,scope_id,enable_third_part, scene) VALUES (UUID_SHORT(), 'functional_default', '', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'ORGANIZATION', '100001', 0, 'FUNCTIONAL');
INSERT INTO template (id,name,remark,internal,update_time,create_time,create_user,scope_type,scope_id,enable_third_part,scene) VALUES (UUID_SHORT(), 'bug_default', '', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'ORGANIZATION', '100001', 0, 'BUG');
-- 初始化组织默认模板内置字段, 项目默认模板内置字段
INSERT INTO template_custom_field(id, field_id, template_id, required, pos, system_field, api_field_id, default_value) VALUES(UUID_SHORT(), (select id from custom_field where name = 'functional_priority'), (select id from template where name = 'functional_default'), 1, 0, 0, NULL, NULL);
INSERT INTO template_custom_field(id, field_id, template_id, required, pos, system_field, api_field_id, default_value) VALUES(UUID_SHORT(), (select id from custom_field where name = 'bug_degree'), (select id from template where name = 'bug_default'), 1, 0, 0, NULL, NULL);
-- 初始化默认项目版本
INSERT INTO project_version (id, project_id, name, description, status, latest, publish_time, start_time, end_time, create_time, create_user) VALUES (UUID_SHORT(), '100001100001', 'v1.0', NULL, 'open', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin');
@ -356,15 +366,20 @@ INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'functional_priority' and scope_id = '100001100001'), 'P2', 'P2', 1);
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'functional_priority' and scope_id = '100001100001'), 'P3', 'P3', 1);
-- 初始化项目功能用例模板
-- 初始化项目缺陷严重程度
INSERT INTO custom_field(id, name, scene, `type`, remark, internal, scope_type, create_time, update_time, create_user, scope_id, ref_id) VALUES(UUID_SHORT(), 'bug_degree', 'BUG', 'SELECT', '', 1, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', '100001100001', (SELECT id FROM (SELECT * FROM custom_field) t where name = 'bug_degree'));
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'bug_degree' and scope_id = '100001100001'), UUID_SHORT(), '提示', 1);
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'bug_degree' and scope_id = '100001100001'), UUID_SHORT(), '一般', 1);
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'bug_degree' and scope_id = '100001100001'), UUID_SHORT(), '严重', 1);
INSERT INTO custom_field_option (field_id,value,`text`,internal) VALUES ((select id from custom_field where name = 'bug_degree' and scope_id = '100001100001'), UUID_SHORT(), '致命', 1);
-- 初始化项目功能用例默认模板, 缺陷默认模板
INSERT INTO template (id,name,remark,internal,update_time,create_time,create_user,scope_type,scope_id,enable_third_part, scene, ref_id) VALUES (UUID_SHORT(), 'functional_default', '', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', '100001100001', 0, 'FUNCTIONAL', (SELECT id FROM (SELECT * FROM template) t where name = 'functional_default'));
INSERT INTO template_custom_field(id, field_id, template_id, required, pos, system_field, api_field_id, default_value) VALUES(UUID_SHORT(), (select id from custom_field where name = 'functional_priority' and scope_id = '100001100001'), (select id from template where name = 'functional_default' and scope_id = '100001100001'), 1, 0, 0, NULL, null);
-- 初始化组织缺陷模板
INSERT INTO template (id,name,remark,internal,update_time,create_time,create_user,scope_type,scope_id,enable_third_part,scene) VALUES (UUID_SHORT(), 'bug_default', '', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'ORGANIZATION', '100001', 0, 'BUG');
-- 初始化项目缺陷模板
INSERT INTO template (id,name,remark,internal,update_time,create_time,create_user,scope_type,scope_id,enable_third_part, scene, ref_id) VALUES (UUID_SHORT(), 'bug_default', '', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', '100001100001', 0, 'BUG', (SELECT id FROM (SELECT * FROM template) t where name = 'bug_default'));
-- 初始化项目默认模板内置字段, 项目默认模板内置字段
INSERT INTO template_custom_field(id, field_id, template_id, required, pos, system_field, api_field_id, default_value) VALUES(UUID_SHORT(), (select id from custom_field where name = 'functional_priority' and scope_id = '100001100001'), (select id from template where name = 'functional_default' and scope_id = '100001100001'), 1, 0, 0, NULL, null);
INSERT INTO template_custom_field(id, field_id, template_id, required, pos, system_field, api_field_id, default_value) VALUES(UUID_SHORT(), (select id from custom_field where name = 'bug_degree' and scope_id = '100001100001'), (select id from template where name = 'bug_default' and scope_id = '100001100001'), 1, 0, 0, NULL, null);
-- 初始化组织接口模板
INSERT INTO template (id,name,remark,internal,update_time,create_time,create_user,scope_type,scope_id,enable_third_part,scene) VALUES (UUID_SHORT(), 'api_default', '', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'ORGANIZATION', '100001', 0, 'API');

View File

@ -5,6 +5,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author wx
@ -34,7 +35,7 @@ public class BugProviderDTO implements Serializable {
private String status;
@Schema(description = "标签")
private String tags;
private List<String> tags;
@Schema(description = "创建时间")
private Long createTime;

View File

@ -0,0 +1,26 @@
package io.metersphere.provider;
import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import java.util.List;
public interface BaseAssociateCaseProvider {
/**
* 获取尚未关联的列表
*
* @param testCasePageProviderRequest 用例查询条件
* @return 通用用例返回集合
*/
List<TestCaseProviderDTO> listUnRelatedTestCaseList(TestCasePageProviderRequest testCasePageProviderRequest);
/**
* 根据关联条件获取关联的用例ID
* @param request 请求参数
* @param deleted 是否删除状态
* @return 用例ID集合
*/
List<String> getRelatedIdsByParam(AssociateOtherCaseRequest request, boolean deleted);
}

View File

@ -53,6 +53,8 @@ bug_local_attachment.file_id.not_blank=文件ID不能为空
bug_local_attachment.file_id.length_range=文件ID长度必须在1-50之间
bug_local_attachment.file_name.not_blank=文件名称不能为空
bug_local_attachment.file_name.length_range=文件名称长度必须在1-50之间
bug_local_attachment.source.not_blank=文件来源不能为空
bug_local_attachment.source.length_range=文件来源长度必须在1-50之间
bug_local_attachment.create_user.not_blank=创建人不能为空
bug_local_attachment.create_user.length_range=创建人长度必须在1-50之间

View File

@ -53,6 +53,8 @@ bug_local_attachment.file_id.not_blank=fileId cannot be empty
bug_local_attachment.file_id.length_range=fileId length must be between 1-50
bug_local_attachment.file_name.not_blank=fileName cannot be empty
bug_local_attachment.file_name.length_range=fileName length must be between 1-50
bug_local_attachment.source.not_blank=source cannot be empty
bug_local_attachment.source.length_range=source length must be between 1-50
bug_local_attachment.create_user.not_blank=createUser cannot be empty
bug_local_attachment.create_user.length_range=createUser length must be between 1-50

View File

@ -53,6 +53,8 @@ bug_local_attachment.file_id.not_blank=文件ID不能为空
bug_local_attachment.file_id.length_range=文件ID长度必须在1-50之间
bug_local_attachment.file_name.not_blank=文件名称不能为空
bug_local_attachment.file_name.length_range=文件名称长度必须在1-50之间
bug_local_attachment.source.not_blank=文件来源不能为空
bug_local_attachment.source.length_range=文件来源长度必须在1-50之间
bug_local_attachment.create_user.not_blank=创建人不能为空
bug_local_attachment.create_user.length_range=创建人长度必须在1-50之间

View File

@ -53,6 +53,8 @@ bug_local_attachment.file_id.not_blank=文件ID不能為空
bug_local_attachment.file_id.length_range=文件ID長度必須在1-50之間
bug_local_attachment.file_name.not_blank=文件名称不能為空
bug_local_attachment.file_name.length_range=文件名称長度必須在1-50之間
bug_local_attachment.source.not_blank=文件来源不能為空
bug_local_attachment.source.length_range=文件来源長度必須在1-50之間
bug_local_attachment.create_user.not_blank=创建人不能為空
bug_local_attachment.create_user.length_range=创建人長度必須在1-50之間

View File

@ -456,6 +456,7 @@ template_scene_illegal_error=使用场景不合法
# 内置的模板或字段
custom_field.functional_priority=用例等级
custom_field.bug_degree=严重程度
template.default=默认模板
parent.node.not_blank=父节点不能为空

View File

@ -460,6 +460,7 @@ scheduled_tasks=Scheduled Tasks
template_scene_illegal_error=Scene is illegal
# 内置的模板或字段
custom_field.functional_priority=Case Priority
custom_field.bug_degree=Bug Degree
template.default=Default
set_default_template=Set the default template

View File

@ -458,6 +458,7 @@ scheduled_tasks=定时任务
template_scene_illegal_error=使用场景不合法
# 内置的模板或字段
custom_field.functional_priority=用例等级
custom_field.bug_degree=严重程度
template.default=默认模板
set_default_template=设置默认模板

View File

@ -457,6 +457,7 @@ template_scene_illegal_error=使用場景不合法
# 内置的模板或字段
custom_field.functional_priority=用例等級
custom_field.bug_degree=嚴重程度
template.default=默認模板
set_default_template=設置默認模板

View File

@ -3,14 +3,18 @@ package io.metersphere.bug.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.bug.constants.BugExportColumns;
import io.metersphere.bug.dto.BugSyncResult;
import io.metersphere.bug.dto.request.*;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.service.BugService;
import io.metersphere.bug.service.BugStatusService;
import io.metersphere.bug.service.BugSyncService;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.project.dto.ProjectTemplateOptionDTO;
import io.metersphere.project.service.ProjectTemplateService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.TemplateScene;
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
import io.metersphere.system.dto.sdk.TemplateDTO;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
@ -42,8 +46,31 @@ public class BugController {
@Resource
private BugSyncService bugSyncService;
@Resource
private BugStatusService bugStatusService;
@Resource
private ProjectTemplateService projectTemplateService;
@GetMapping("/header/custom-field/{projectId}")
@Operation(summary = "缺陷管理-获取表头自定义字段")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public List<TemplateCustomFieldDTO> getHeaderFields(@PathVariable String projectId) {
return bugService.getHeaderCustomFields(projectId);
}
@GetMapping("/header/status-option/{projectId}")
@Operation(summary = "缺陷管理-获取表头状态选项")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public List<SelectOption> getHeaderStatusOption(@PathVariable String projectId) {
return bugStatusService.getHeaderStatusOption(projectId);
}
@GetMapping("/header/handler-option/{projectId}")
@Operation(summary = "缺陷管理-获取表头处理人选项")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public List<SelectOption> getHeaderHandleOption(@PathVariable String projectId) {
return bugService.getHeaderHandlerOption(projectId);
}
@PostMapping("/page")
@Operation(summary = "缺陷管理-获取缺陷列表")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@ -51,7 +78,7 @@ public class BugController {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
request.setUseTrash(false);
return PageUtils.setPageInfo(page, bugService.list(request, SessionUtils.getUserId()));
return PageUtils.setPageInfo(page, bugService.list(request));
}
@PostMapping("/add")
@ -95,13 +122,15 @@ public class BugController {
@Operation(summary = "缺陷管理-批量删除缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_DELETE)
public void batchDelete(@Validated @RequestBody BugBatchRequest request) {
bugService.batchDelete(request, SessionUtils.getUserId());
request.setUseTrash(false);
bugService.batchDelete(request);
}
@PostMapping("/batch-update")
@Operation(summary = "缺陷管理-批量编辑缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public void batchUpdate(@Validated @RequestBody BugBatchUpdateRequest request) {
request.setUseTrash(false);
bugService.batchUpdate(request, SessionUtils.getUserId());
}
@ -133,6 +162,13 @@ public class BugController {
bugSyncService.syncAllBugs(request);
}
@GetMapping("/sync/check/{projectId}")
@Operation(summary = "缺陷管理-同步状态校验")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public BugSyncResult checkStatus(@PathVariable String projectId) {
return bugSyncService.checkSyncStatus(projectId);
}
@GetMapping("/export/columns/{projectId}")
@Operation(summary = "缺陷管理-获取导出字段配置")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_EXPORT)
@ -144,6 +180,6 @@ public class BugController {
@Operation(summary = "缺陷管理-批量导出缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_EXPORT)
public ResponseEntity<byte[]> export(@Validated @RequestBody BugExportRequest request) throws Exception {
return bugService.export(request, SessionUtils.getUserId());
return bugService.export(request);
}
}

View File

@ -2,10 +2,17 @@ package io.metersphere.bug.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.bug.dto.request.BugRelateCaseModuleRequest;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.service.BugRelateCaseService;
import io.metersphere.bug.service.BugRelateCaseCommonService;
import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.provider.BaseAssociateCaseProvider;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
import io.metersphere.system.utils.SessionUtils;
@ -17,33 +24,69 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "缺陷管理-关联用例")
@RestController
@RequestMapping("/bug/relate/case")
@RequestMapping("/bug/case")
public class BugRelateCaseController {
@Resource
private BugRelateCaseService bugRelateCaseService;
private BugRelateCaseCommonService bugRelateCaseCommonService;
@Resource
private BaseAssociateCaseProvider functionalCaseProvider;
@PostMapping("/un-relate/page")
@Operation(description = "缺陷管理-关联用例-未关联用例-列表分页")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public Pager<List<TestCaseProviderDTO>> unRelatedPage(@Validated @RequestBody TestCasePageProviderRequest request) {
// 目前只保留功能用例的Provider接口, 后续其他用例根据RelateCaseType扩展
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), null);
return PageUtils.setPageInfo(page, functionalCaseProvider.listUnRelatedTestCaseList(request));
}
@PostMapping("/un-relate/module/tree")
@Operation(summary = "缺陷管理-关联用例-未关联用例-模块树")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.projectId", resourceType = "project")
public List<BaseTreeNode> getTree(@RequestBody @Validated BugRelateCaseModuleRequest request) {
return bugRelateCaseCommonService.getRelateCaseTree(request);
}
@PostMapping("/un-relate/module/count")
@Operation(summary = "缺陷管理-关联用例-未关联用例-模块树数量")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.projectId", resourceType = "project")
public Map<String, Long> countTree(@RequestBody @Validated BugRelateCaseModuleRequest request) {
return bugRelateCaseCommonService.countTree(request);
}
@PostMapping("/relate")
@Operation(summary = "缺陷管理-关联用例-关联")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.sourceId", resourceType = "functional_case")
public void relate(@Validated @RequestBody AssociateOtherCaseRequest request) {
bugRelateCaseCommonService.relateCase(request, false, SessionUtils.getUserId());
}
@PostMapping("/page")
@Operation(description = "缺陷管理-关联用例-列表分页查询")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public Pager<List<BugRelateCaseDTO>> page(@Validated @RequestBody BugRelatedCasePageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize());
return PageUtils.setPageInfo(page, bugRelateCaseService.page(request));
return PageUtils.setPageInfo(page, bugRelateCaseCommonService.page(request));
}
@GetMapping("/un-relate/{id}")
@Operation(description = "缺陷管理-关联用例-取消关联用例")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public void unRelate(@PathVariable String id) {
bugRelateCaseService.unRelate(id);
bugRelateCaseCommonService.unRelate(id);
}
@GetMapping("/check-permission/{projectId}/{caseType}")
@Operation(description = "缺陷管理-关联用例-查看用例权限校验")
public void checkPermission(@PathVariable String projectId, @PathVariable String caseType) {
bugRelateCaseService.checkPermission(projectId, SessionUtils.getUserId(), caseType);
bugRelateCaseCommonService.checkPermission(projectId, SessionUtils.getUserId(), caseType);
}
}

View File

@ -0,0 +1,69 @@
package io.metersphere.bug.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.bug.dto.request.BugBatchRequest;
import io.metersphere.bug.dto.request.BugPageRequest;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.service.BugService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "缺陷管理")
@RestController
@RequestMapping("/bug/trash")
public class BugTrashController {
@Resource
private BugService bugService;
@PostMapping("/page")
@Operation(summary = "回收站-获取缺陷列表")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public Pager<List<BugDTO>> page(@Validated @RequestBody BugPageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
request.setUseTrash(true);
return PageUtils.setPageInfo(page, bugService.list(request));
}
@GetMapping("/recover/{id}")
@Operation(summary = "回收站-恢复")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public void recover(@PathVariable String id) {
bugService.recover(id);
}
@GetMapping("/delete/{id}")
@Operation(summary = "回收站-彻底删除")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_DELETE)
public void deleteTrash(@PathVariable String id) {
bugService.deleteTrash(id);
}
@PostMapping("/batch-recover")
@Operation(summary = "回收站-批量恢复")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public void batchRecover(@Validated @RequestBody BugBatchRequest request) {
request.setUseTrash(true);
bugService.batchRecover(request);
}
@PostMapping("/batch-delete")
@Operation(summary = "回收站-批量彻底删除")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_DELETE)
public void batchDelete(@Validated @RequestBody BugBatchRequest request) {
request.setUseTrash(true);
bugService.batchDeleteTrash(request);
}
}

View File

@ -0,0 +1,18 @@
package io.metersphere.bug.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
@Data
@Builder
public class BugSyncResult implements Serializable {
@Schema(description = "是否同步完成")
private Boolean complete;
@Schema(description = "是否同步完成")
private String msg;
}

View File

@ -6,6 +6,7 @@ import lombok.Data;
public class BugTemplateInjectField {
private String id;
private String name;
private String key;
private String type;
private String defaultValue;
private Boolean required;

View File

@ -11,4 +11,7 @@ public class BugBatchRequest extends TableBatchProcessDTO {
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String projectId;
@Schema(description = "是否回收站")
private boolean useTrash;
}

View File

@ -4,13 +4,21 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
public class BugBatchUpdateRequest extends BugBatchRequest{
@Schema(description = "标签")
private String tag;
@Schema(description = "标签内容")
private List<String> tags;
@Schema(description = "是否追加", requiredMode = Schema.RequiredMode.REQUIRED)
private boolean append;
@Schema(description = "更新人")
private String updateUser;
@Schema(description = "更新时间")
private Long updateTime;
}

View File

@ -38,7 +38,7 @@ public class BugEditRequest {
private String templateId;
@Schema(description = "标签")
private String tag;
private List<String> tags;
@Schema(description = "缺陷内容")
private String description;

View File

@ -0,0 +1,42 @@
package io.metersphere.bug.dto.request;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.system.dto.sdk.BaseCondition;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Data
public class BugRelateCaseModuleRequest extends BaseCondition {
@Schema(description = "模块ID(根据模块树查询时要把当前节点以及子节点都放在这里。)")
private List<@NotBlank String> moduleIds;
@Schema(description = "协议", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition_module.protocol.not_blank}")
@Size(min = 1, max = 20, message = "{api_definition_module.protocol.length_range}")
private String protocol = ModuleConstants.NODE_PROTOCOL_HTTP;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition_module.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_definition_module.project_id.length_range}")
private String projectId;
@Schema(description = "关键字")
private String keyword;
@Schema(description = "版本fk")
private String versionId;
@Schema(description = "来源缺陷ID")
@NotBlank(message = "{api_definition.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_definition.project_id.length_range}")
private String sourceId;
@Schema(description = "关联用例来源类型(FUNCTIONAL, API, SCENARIO, UI, PERFORMANCE)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{associate_other_case_request.type.not_blank}")
private String sourceType;
}

View File

@ -27,9 +27,6 @@ public class BugDTO extends Bug {
@Schema(description = "处理人名称")
private String handleUserName;
@Schema(description = "状态名称")
private String statusName;
@Schema(description = "关联用例数量")
private Integer relationCaseCount;

View File

@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
public class BugTagEditDTO {
@ -12,5 +14,5 @@ public class BugTagEditDTO {
private String bugId;
@Schema(description = "标签值")
private String tag;
private List<String> tags;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.bug.enums;
public enum BugAttachmentSourceType {
/**
* 附件
*/
ATTACHMENT,
/**
* MarkDown编辑器
*/
MD;
}

View File

@ -5,7 +5,7 @@
insert into bug_local_attachment values
<foreach collection="list" item="attachment" separator=",">
(#{attachment.id}, #{attachment.bugId}, #{attachment.fileId}, #{attachment.fileName},
#{attachment.size}, #{attachment.createUser}, #{attachment.createTime})
#{attachment.size}, #{attachment.source}, #{attachment.createUser}, #{attachment.createTime})
</foreach>
</insert>
</mapper>

View File

@ -1,9 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.bug.mapper.ExtBugMapper">
<select id="list" resultType="io.metersphere.bug.dto.response.BugDTO">
<resultMap id="BugDTO" type="io.metersphere.bug.dto.response.BugDTO">
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
</resultMap>
<resultMap id="BugTagEditDTO" type="io.metersphere.bug.dto.response.BugTagEditDTO">
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
</resultMap>
<resultMap id="BugProviderDTO" type="io.metersphere.dto.BugProviderDTO">
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
</resultMap>
<select id="list" resultMap="BugDTO">
select b.id, b.num, b.title, b.handle_user, b.create_user, b.create_time, b.update_time, b.delete_time, b.delete_user,
b.project_id, b.template_id, b.platform, b.status, bc.description from bug b left join bug_content bc on b.id = bc.bug_id
b.project_id, b.template_id, b.platform, b.status, b.tags, bc.description from bug b left join bug_content bc on b.id = bc.bug_id
<include refid="queryWhereCondition"/>
</select>
@ -12,7 +24,7 @@
<include refid="queryWhereCondition"/>
</select>
<select id="listByIds" resultType="io.metersphere.bug.dto.response.BugDTO">
<select id="listByIds" resultMap="BugDTO">
select b.id,
b.num,
b.title,
@ -26,6 +38,7 @@
b.template_id,
b.platform,
b.status,
b.tags,
bc.description
from bug b
left join bug_content bc on b.id = bc.bug_id
@ -34,12 +47,13 @@
#{id}
</foreach>
</select>
<select id="getMaxNum" resultType="java.lang.Long">
select max(num) from bug where project_id = #{projectId}
</select>
<select id="getBugTagList" resultType="io.metersphere.bug.dto.response.BugTagEditDTO">
select id as bugId, tag from bug where id in
<select id="getBugTagList" resultMap="BugTagEditDTO">
select id as bugId, tags from bug where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
@ -48,8 +62,14 @@
<update id="batchUpdate" parameterType="io.metersphere.bug.dto.request.BugBatchUpdateRequest">
update bug
<set>
<if test="request.tag != null and request.tag != ''">
tag = #{request.tag},
<if test="request.tags != null and request.tags != ''">
tags = #{request.tags, typeHandler=io.metersphere.handler.ListTypeHandler},
</if>
<if test="request.updateUser != null and request.updateUser != ''">
update_user = #{updateUser},
</if>
<if test="request.updateTime != null">
update_time = #{updateTime},
</if>
</set>
where id in
@ -58,6 +78,34 @@
</foreach>
</update>
<select id="listByProviderRequest" resultMap="BugProviderDTO">
SELECT
b.id as id,
b.title name,
b.handle_user handleUser,
b.`status` as status,
b.tags as tag,
b.create_time createTime
FROM
bug b
WHERE
b.deleted = #{deleted}
AND b.id NOT IN
(
select associate.${bugColumnName} from ${table} associate where associate.${sourceName} = #{request.sourceId}
)
<include refid="queryWhereConditionByProvider"/>
</select>
<select id="getIdsByProvider" resultType="java.lang.String">
SELECT
b.id
FROM
bug b
WHERE b.deleted =#{deleted}
<include refid="queryWhereConditionByProvider"/>
</select>
<sql id="queryWhereCondition">
<where>
<if test="request.useTrash">
@ -73,11 +121,14 @@
and (
b.title like concat('%', #{request.keyword},'%')
or b.num like concat('%', #{request.keyword},'%')
or b.tags like concat('%', #{request.keyword},'%')
)
</if>
<include refid="filter"/>
<include refid="combine">
<property name="condition" value="request.combine"/>
<property name="searchMode" value="request.searchMode"/>
<property name="combineTag" value="request.combine.tag"/>
</include>
</where>
</sql>
@ -87,22 +138,27 @@
<foreach collection="request.filter.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<!-- 处理人 -->
<when test="key == 'handleUser'">
and b.handle_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 创建人 -->
<when test="key == 'createUser'">
and b.create_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 平台 -->
<when test="key == 'platform'">
and b.platform in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 状态 -->
<when test="key == 'status'">
and b.status in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 自定义单选字段 -->
<when test="key.startsWith('custom_single')">
and b.id in (
select bug_id from bug_custom_field where concat('custom_single_', field_id) = #{key}
@ -110,7 +166,8 @@
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
)
</when>
<when test="key.startsWith('custom_multiple')">
<!-- 自定义多选字段 -->
<when test="key.startsWith('custom_multiple_321421')">
and b.id in (
select bug_id from bug_custom_field where concat('custom_multiple_', field_id) = #{key}
and
@ -125,98 +182,181 @@
<sql id="combine">
<if test="request.combine != null">
<if test='${condition}.handleUser != null'>
and b.handle_user
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.handleUser"/>
and (
<!-- 任意/所有拼接 -->
<include refid="prefixMode">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<if test='${condition}.createUser != null'>
and b.create_user
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.createUser"/>
</include>
</if>
<if test='${condition}.platform != null'>
and b.platform
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.platform"/>
</include>
</if>
<if test='${condition}.status != null'>
and b.status
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.platform"/>
</include>
</if>
<if test="${condition}.customs != null and ${condition}.customs.size() > 0">
<foreach collection="${condition}.customs" item="custom" separator="" open="" close="">
<if test="custom.value != ''">
<if test='custom.operator == "not like" or custom.operator == "not in"'>
and b.id not in (
<!-- ID -->
<if test='${condition}.num != null'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
b.num
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.num"/>
</include>
</if>
<!-- 名称 -->
<if test='${condition}.title != null'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
b.title
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.title"/>
</include>
</if>
<!-- 所属平台 -->
<if test='${condition}.platform != null'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
b.platform
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.platform"/>
</include>
</if>
<!-- 处理人 -->
<if test='${condition}.handleUser != null'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
b.handle_user
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.handleUser"/>
</include>
</if>
<!-- 状态 -->
<if test='${condition}.status != null'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
b.status
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.status"/>
</include>
</if>
<!-- 创建人 -->
<if test='${condition}.createUser != null'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
b.create_user
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.createUser"/>
</include>
</if>
<!-- 创建时间 -->
<if test='${condition}.createTime != null'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
b.create_time
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.createTime"/>
</include>
</if>
<!-- 标签 -->
<if test='${condition}.tags != null'>
<include refid="queryTag">
<property name="searchMode" value="${searchMode}"/>
<property name="combineTag" value="${condition}.tags"/>
</include>
</if>
<!-- 自定义字段 -->
<if test="${condition}.customs != null and ${condition}.customs.size() > 0">
<foreach collection="${condition}.customs" item="custom" separator="" open="" close="">
<if test="custom.value != ''">
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
<if test='custom.operator == "not like" or custom.operator == "not in"'>
b.id not in (
</if>
<if test='custom.operator != "not like" and custom.operator != "not in"'>
b.id in (
</if>
select bug_id from bug_custom_field where field_id = #{custom.id} and
<choose>
<when test="custom.type == 'array'">
<foreach collection="custom.value" item="val" separator="or" open="(" close=")">
JSON_CONTAINS(`value`, JSON_ARRAY(#{val}))
</foreach>
</when>
<when test="custom.type == 'time'">
left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13)
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</when>
<otherwise>
trim(both '"' from `value`)
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</otherwise>
</choose>
)
</if>
<if test='custom.operator != "not like" and custom.operator != "not in"'>
and b.id in (
</if>
select bug_id from bug_custom_field where field_id = #{custom.id}
<choose>
<when test="custom.type == 'richText' or custom.type == 'textarea' or custom.operator == 'current user'">
and `value`
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</when>
<when test="custom.type == 'multipleMember' or custom.type == 'checkbox' or custom.type == 'multipleSelect'">
and ${custom.value}
</when>
<when test="custom.type == 'date' or custom.type == 'datetime'">
and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13)
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</when>
<otherwise>
and trim(both '"' from `value`)
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</otherwise>
</choose>
)
</if>
</foreach>
</if>
</foreach>
</if>
)
</if>
</sql>
<select id="listByProviderRequest" resultType="io.metersphere.dto.BugProviderDTO">
SELECT
b.id id,
b.title name,
b.handle_user handleUser,
b.`status` status,
b.tag tag,
b.create_time createTime
FROM
bug b
WHERE
b.deleted = #{deleted}
AND b.id NOT IN
<sql id="prefixMode">
<choose>
<when test='${searchMode} == "AND"'>
1 = 1
</when>
<when test='${searchMode} == "OR"'>
1 = 2
</when>
</choose>
</sql>
<sql id="queryType">
<choose>
<when test='${searchMode} == "AND"'>
and
</when>
<when test='${searchMode} == "OR"'>
or
</when>
</choose>
</sql>
<sql id="queryTag">
<!-- 不包含 -->
<if test='${combineTag}.value.size() > 0 and ${combineTag}.operator == "not like"'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
(
select associate.${bugColumnName} from ${table} associate where associate.${sourceName} = #{request.sourceId}
b.tags is null or b.tags = '[]' or
<foreach collection="${combineTag}.value" item="tag" separator="and" open="(" close=")">
!JSON_CONTAINS(b.tags, JSON_ARRAY(#{tag}))
</foreach>
)
<include refid="queryWhereConditionByProvider"/>
</select>
<select id="getIdsByProvider" resultType="java.lang.String">
SELECT
b.id
FROM
bug b
WHERE b.deleted =#{deleted}
<include refid="queryWhereConditionByProvider"/>
</select>
</if>
<!-- 包含 -->
<if test='${combineTag}.value.size() > 0 and ${combineTag}.operator == "like"'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
<foreach collection="${combineTag}.value" item="tag" separator="or" open="(" close=")">
JSON_CONTAINS(b.tags, JSON_ARRAY(#{tag}))
</foreach>
</if>
<!---->
<if test='${combineTag}.operator == "is null"'>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
(b.tags is null or b.tags = '[]')
</if>
</sql>
<sql id="queryWhereConditionByProvider">
<if test="request.projectId != null">

View File

@ -1,10 +1,13 @@
package io.metersphere.bug.mapper;
import io.metersphere.bug.dto.request.BugRelateCaseModuleRequest;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseCountDTO;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.request.AssociateBugPageRequest;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -14,6 +17,22 @@ import java.util.List;
*/
public interface ExtBugRelateCaseMapper {
/**
* 获取缺陷关联的用例模块树
* @param request 请求参数
* @param deleted 是否删除状态
* @return 模块树集合
*/
List<BaseTreeNode> getRelateCaseModule(@Param("request") BugRelateCaseModuleRequest request, @Param("deleted") boolean deleted);
/**
* 获取缺陷关联的用例模块树数量
* @param request 请求参数
* @param deleted 是否删除状态
* @return 模块树数量
*/
List<ModuleCountDTO> countRelateCaseModuleTree(@Param("request") BugRelateCaseModuleRequest request, @Param("deleted") boolean deleted);
/**
* 统计缺陷关联的用例数量
*

View File

@ -1,6 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.bug.mapper.ExtBugRelateCaseMapper">
<select id="getRelateCaseModule" resultType="io.metersphere.system.dto.sdk.BaseTreeNode">
select
fcm.id,
fcm.parent_id as parentId,
fcm.name,
fcm.pos,
fcm.project_id
from functional_case_module fcm left join functional_case fc on fc.module_id = fcm.id
where fc.deleted = #{deleted}
and fc.project_id = #{request.projectId}
and fc.version_id = #{request.versionId}
and fc.id not in
(
select brc.case_id from bug_relation_case brc where brc.bug_id = #{request.sourceId} and brc.case_type = #{request.sourceType}
)
<include refid="queryModuleWhereCondition"/>
order by pos
</select>
<select id="countRelateCaseModuleTree" resultType="io.metersphere.project.dto.ModuleCountDTO">
select
fcm.id as moduleId,
count(fc.id) as dataCount
from functional_case_module fcm left join functional_case fc on fc.module_id = fcm.id
where fc.deleted = #{deleted}
and fc.project_id = #{request.projectId}
and fc.version_id = #{request.versionId}
and fc.id not in
(
select brc.case_id from bug_relation_case brc where brc.bug_id = #{request.sourceId} and brc.case_type = #{request.sourceType}
)
<include refid="queryModuleWhereCondition"/>
group by fcm.id
</select>
<select id="countRelationCases" resultType="io.metersphere.bug.dto.response.BugRelateCaseCountDTO">
select count(id) as relationCaseCount, bug_id as bugId from bug_relation_case brc
where brc.bug_id in
@ -22,9 +57,9 @@
<select id="getAssociateBugs" resultType="io.metersphere.dto.BugProviderDTO">
SELECT
brc.id id,
brc.id as id,
brc.bug_id bugId,
b.title name,
b.title as name,
b.handle_user handleUser,
b.`status`,
brc.test_plan_id testPlanId,
@ -52,4 +87,19 @@
and b.title like concat('%', #{request.keyword},'%')
</if>
</sql>
<sql id="queryModuleWhereCondition">
<!-- 待补充关联Case弹窗中的高级搜索条件 -->
<if test="request.keyword != null and request.keyword != ''">
and (
fc.id like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%')
)
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and fcm.module_id in
<foreach collection="moduleIds" item="moduleId" open="(" separator="," close=")">
#{moduleId}
</foreach>
</if>
</sql>
</mapper>

View File

@ -5,7 +5,7 @@ import io.metersphere.bug.domain.BugRelationCase;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.bug.mapper.ExtBugMapper;
import io.metersphere.bug.mapper.ExtBugRelateCaseMapper;
import io.metersphere.bug.service.BugRelateCaseService;
import io.metersphere.bug.service.BugRelateCaseCommonService;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.provider.BaseAssociateBugProvider;
import io.metersphere.request.AssociateBugPageRequest;
@ -29,7 +29,7 @@ public class AssociateBugProvider implements BaseAssociateBugProvider {
@Resource
private BugRelationCaseMapper bugRelationCaseMapper;
@Resource
private BugRelateCaseService bugRelateCaseService;
private BugRelateCaseCommonService bugRelateCaseCommonService;
@Resource
private ExtBugRelateCaseMapper extBugRelateCaseMapper;
@ -72,7 +72,7 @@ public class AssociateBugProvider implements BaseAssociateBugProvider {
@Override
public void disassociateBug(String id) {
bugRelateCaseService.unRelate(id);
bugRelateCaseCommonService.unRelate(id);
}
@Override

View File

@ -1,6 +1,8 @@
package io.metersphere.bug.service;
import io.metersphere.bug.domain.BugRelationCase;
import io.metersphere.bug.domain.BugRelationCaseExample;
import io.metersphere.bug.dto.request.BugRelateCaseModuleRequest;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
@ -9,16 +11,26 @@ import io.metersphere.project.domain.Project;
import io.metersphere.project.domain.ProjectExample;
import io.metersphere.project.domain.ProjectVersion;
import io.metersphere.project.domain.ProjectVersionExample;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.project.mapper.ProjectVersionMapper;
import io.metersphere.project.service.ModuleTreeService;
import io.metersphere.project.service.PermissionCheckService;
import io.metersphere.provider.BaseAssociateCaseProvider;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.sdk.constants.CaseType;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.BaseTreeNode;
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 org.springframework.transaction.annotation.Transactional;
@ -29,11 +41,13 @@ import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class BugRelateCaseService {
public class BugRelateCaseCommonService extends ModuleTreeService {
@Resource
private ProjectMapper projectMapper;
@Resource
private SqlSessionFactory sqlSessionFactory;
@Resource
private ProjectVersionMapper projectVersionMapper;
@Resource
private BugRelationCaseMapper bugRelationCaseMapper;
@ -41,6 +55,80 @@ public class BugRelateCaseService {
private ExtBugRelateCaseMapper extBugRelateCaseMapper;
@Resource
private PermissionCheckService permissionCheckService;
@Resource
private BaseAssociateCaseProvider functionalCaseProvider;
/**
* 获取关联用例模块树(不包括数量)
* @param request 请求参数
* @return 模块树集合
*/
public List<BaseTreeNode> getRelateCaseTree(BugRelateCaseModuleRequest request) {
// 目前只保留功能用例的左侧模块树方法调用, 后续其他用例根据RelateCaseType扩展
List<BaseTreeNode> relateCaseModules = extBugRelateCaseMapper.getRelateCaseModule(request, false);
// 构建模块树层级数量为通用逻辑
return super.buildTreeAndCountResource(relateCaseModules, true, Translator.get("api_unplanned_request"));
}
/**
* 获取关联用例模块树数量
* @param request 请求参数
* @return 模块树集合
*/
public Map<String, Long> countTree(BugRelateCaseModuleRequest request) {
// 目前只保留功能用例的左侧模块树方法调用, 后续其他用例根据RelateCaseType扩展
List<ModuleCountDTO> moduleCounts = extBugRelateCaseMapper.countRelateCaseModuleTree(request, false);
List<BaseTreeNode> relateCaseModules = extBugRelateCaseMapper.getRelateCaseModule(request, false);
List<BaseTreeNode> relateCaseModuleWithCount = buildTreeAndCountResource(relateCaseModules, moduleCounts, true, Translator.get("api_unplanned_request"));
Map<String, Long> moduleCountMap = getIdCountMapByBreadth(relateCaseModuleWithCount);
long total = getAllCount(moduleCounts);
moduleCountMap.put("total", total);
return moduleCountMap;
}
/**
* 关联用例
* @param request 关联用例参数
* @param deleted 是否删除状态
* @param currentUser 当前用户
*/
public void relateCase(AssociateOtherCaseRequest request, boolean deleted, String currentUser) {
// 目前只需根据关联条件获取功能用例ID, 后续扩展
List<String> relatedIds = functionalCaseProvider.getRelatedIdsByParam(request, deleted);
// 缺陷关联用例通用逻辑
if (CollectionUtils.isEmpty(relatedIds)) {
return;
}
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
BugRelationCaseMapper relationCaseMapper = sqlSession.getMapper(BugRelationCaseMapper.class);
// 根据用例ID筛选出已通过测试计划关联的用例
BugRelationCaseExample bugRelationCaseExample = new BugRelationCaseExample();
bugRelationCaseExample.createCriteria().andTestPlanCaseIdIn(relatedIds);
List<BugRelationCase> planRelatedCases = bugRelationCaseMapper.selectByExample(bugRelationCaseExample);
Map<String, String> planRelatedMap = planRelatedCases.stream().collect(Collectors.toMap(BugRelationCase::getTestPlanCaseId, BugRelationCase::getId));
relatedIds.forEach(relatedId -> {
if (planRelatedMap.containsKey(relatedId)) {
// 计划已关联
BugRelationCase record = new BugRelationCase();
record.setId(planRelatedMap.get(relatedId));
record.setCaseId(relatedId);
record.setUpdateTime(System.currentTimeMillis());
relationCaseMapper.updateByPrimaryKeySelective(record);
} else {
BugRelationCase record = new BugRelationCase();
record.setId(IDGenerator.nextStr());
record.setCaseId(relatedId);
record.setBugId(request.getSourceId());
record.setCaseType(request.getSourceType());
record.setCreateUser(currentUser);
record.setCreateTime(System.currentTimeMillis());
record.setUpdateTime(System.currentTimeMillis());
relationCaseMapper.insert(record);
}
});
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
/**
* 分页查询关联用例列表
@ -133,4 +221,14 @@ public class BugRelateCaseService {
List<ProjectVersion> projectVersions = projectVersionMapper.selectByExample(projectVersionExample);
return projectVersions.stream().collect(Collectors.toMap(ProjectVersion::getId, ProjectVersion::getName));
}
@Override
public void updatePos(String id, long pos) {
}
@Override
public void refreshPos(String parentId) {
}
}

View File

@ -8,6 +8,7 @@ import io.metersphere.bug.dto.response.BugCustomFieldDTO;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.dto.response.BugRelateCaseCountDTO;
import io.metersphere.bug.dto.response.BugTagEditDTO;
import io.metersphere.bug.enums.BugAttachmentSourceType;
import io.metersphere.bug.enums.BugPlatform;
import io.metersphere.bug.enums.BugTemplateCustomField;
import io.metersphere.bug.mapper.*;
@ -45,10 +46,10 @@ import io.metersphere.system.mapper.TemplateMapper;
import io.metersphere.system.service.*;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.CustomFieldUtils;
import jakarta.annotation.Resource;
import jodd.util.StringUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
@ -61,7 +62,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -72,6 +72,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.metersphere.bug.enums.result.BugResultCode.BUG_NOT_EXIST;
import static io.metersphere.bug.enums.result.BugResultCode.NOT_LOCAL_BUG_ERROR;
/**
* @author song-cc-rock
@ -145,14 +146,13 @@ public class BugService {
* @param request 列表请求参数
* @return 缺陷列表
*/
public List<BugDTO> list(BugPageRequest request, String currentUser) {
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, currentUser);
public List<BugDTO> list(BugPageRequest request) {
List<BugDTO> bugs = extBugMapper.list(request);
if (CollectionUtils.isEmpty(bugs)) {
return new ArrayList<>();
}
// 处理自定义字段及状态字段
List<BugDTO> bugList = handleCustomFieldsAndStatus(bugs, request.getProjectId());
// 处理自定义字段
List<BugDTO> bugList = handleCustomField(bugs, request.getProjectId());
return buildExtraInfo(bugList);
}
@ -209,10 +209,7 @@ public class BugService {
* @param id 缺陷ID
*/
public void delete(String id) {
Bug bug = bugMapper.selectByPrimaryKey(id);
if (bug == null) {
throw new MSException(BUG_NOT_EXIST);
}
Bug bug = checkById(id);
if (StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
Bug record = new Bug();
record.setId(id);
@ -229,6 +226,33 @@ public class BugService {
}
}
/**
* 恢复缺陷
* @param id 缺陷ID
*/
public void recover(String id) {
Bug bug = checkById(id);
if (!StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
throw new MSException(NOT_LOCAL_BUG_ERROR);
}
Bug record = new Bug();
record.setId(id);
record.setDeleted(false);
bugMapper.updateByPrimaryKeySelective(record);
}
/**
* 彻底删除缺陷
* @param id 缺陷ID
*/
public void deleteTrash(String id) {
Bug bug = checkById(id);
if (!StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
throw new MSException(NOT_LOCAL_BUG_ERROR);
}
bugMapper.deleteByPrimaryKey(id);
}
/**
* 获取缺陷模板详情
*
@ -260,35 +284,58 @@ public class BugService {
* 批量删除缺陷
* @param request 请求参数
*/
public void batchDelete(BugBatchRequest request, String currentUser) {
List<String> batchIds = getBatchIdsByRequest(request, currentUser);
public void batchDelete(BugBatchRequest request) {
List<String> batchIds = getBatchIdsByRequest(request);
batchIds.forEach(this::delete);
}
/**
* 批量恢复缺陷
* @param request 请求参数
*/
public void batchRecover(BugBatchRequest request) {
List<String> batchIds = getBatchIdsByRequest(request);
batchIds.forEach(this::recover);
}
/**
* 批量彻底删除缺陷
* @param request 请求参数
*/
public void batchDeleteTrash(BugBatchRequest request) {
List<String> batchIds = getBatchIdsByRequest(request);
batchIds.forEach(this::deleteTrash);
}
/**
* 批量编辑缺陷
* @param request 请求参数
* @param currentUser 当前用户
*/
public void batchUpdate(BugBatchUpdateRequest request, String currentUser) {
List<String> batchIds = getBatchIdsByRequest(request, currentUser);
List<String> batchIds = getBatchIdsByRequest(request);
// 目前只做标签的批量编辑
if (request.isAppend()) {
// 标签(追加)
List<BugTagEditDTO> bugTagList = extBugMapper.getBugTagList(batchIds);
Map<String, String> bugTagMap = bugTagList.stream().collect(Collectors.toMap(BugTagEditDTO::getBugId, b -> Optional.ofNullable(b.getTag()).orElse(StringUtils.EMPTY)));
Map<String, List<String>> bugTagMap = bugTagList.stream().collect(Collectors.toMap(BugTagEditDTO::getBugId, BugTagEditDTO::getTags));
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
BugMapper batchMapper = sqlSession.getMapper(BugMapper.class);
bugTagMap.forEach((k, v) -> {
Bug record = new Bug();
record.setId(k);
record.setTag(CustomFieldUtils.appendToMultipleCustomField(v, request.getTag()));
record.setTags(ListUtils.union(v, request.getTags()));
record.setUpdateUser(currentUser);
record.setUpdateTime(System.currentTimeMillis());
batchMapper.updateByPrimaryKeySelective(record);
});
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
} else {
// 标签(覆盖)
request.setUpdateUser(currentUser);
request.setUpdateTime(System.currentTimeMillis());
extBugMapper.batchUpdate(request, batchIds);
}
}
@ -346,7 +393,6 @@ public class BugService {
* @param project 项目
*/
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void syncPlatformBugs(List<Bug> remainBugs, Project project) {
try {
// 分页同步
@ -477,17 +523,14 @@ public class BugService {
// 状态字段
attachTemplateStatusField(templateDTO, projectId, fromStatusId, platformBugKey);
// 处理人字段
// 内置字段(处理人字段)
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
// 获取插件中自定义的注入字段(处理人)
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
// 状态选项获取时, 获取平台校验了服务集成配置, 所以此处不需要再次校验
Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(),
new String(serviceIntegration.getConfiguration()));
AbstractPlatformPlugin platformPlugin = (AbstractPlatformPlugin) pluginLoadService.getMsPluginManager().getPlugin(serviceIntegration.getPluginId()).getPlugin();
Object scriptContent = pluginLoadService.getPluginScriptContent(serviceIntegration.getPluginId(), platformPlugin.getProjectBugTemplateInjectField());
String injectFields = JSON.toJSONString(((Map<?, ?>) scriptContent).get("injectFields"));
List<BugTemplateInjectField> injectFieldList = JSON.parseArray(injectFields, BugTemplateInjectField.class);
List<BugTemplateInjectField> injectFieldList = getPlatformInjectFields(projectId);
for (BugTemplateInjectField injectField : injectFieldList) {
TemplateCustomFieldDTO templateCustomFieldDTO = new TemplateCustomFieldDTO();
BeanUtils.copyBean(templateCustomFieldDTO, injectField);
@ -501,20 +544,11 @@ public class BugService {
}
} else {
// Local(处理人)
ProjectMemberRequest request = new ProjectMemberRequest();
request.setProjectId(projectId);
List<ProjectUserDTO> projectMembers = projectMemberService.listMember(request);
List<SelectOption> projectMemberOptions = projectMembers.stream().map(user -> {
SelectOption option = new SelectOption();
option.setText(user.getName());
option.setValue(user.getId());
return option;
}).toList();
TemplateCustomFieldDTO handleUserField = new TemplateCustomFieldDTO();
handleUserField.setFieldId(BugTemplateCustomField.HANDLE_USER.getId());
handleUserField.setFieldName(BugTemplateCustomField.HANDLE_USER.getName());
handleUserField.setType(CustomFieldType.SELECT.getType());
handleUserField.setPlatformOptionJson(JSON.toJSONString(projectMemberOptions));
handleUserField.setPlatformOptionJson(JSON.toJSONString(getLocalHandlerOption(projectId)));
handleUserField.setRequired(true);
templateDTO.getCustomFields().addFirst(handleUserField);
}
@ -614,7 +648,9 @@ public class BugService {
} else {
Bug orignalBug = checkBugExist(request.getId());
// 追加处理人
bug.setHandleUsers(orignalBug.getHandleUsers() + "," + bug.getHandleUser());
if (!StringUtils.equals(orignalBug.getHandleUser(), bug.getHandleUser())) {
bug.setHandleUsers(orignalBug.getHandleUsers() + "," + bug.getHandleUser());
}
bug.setUpdateUser(currentUser);
bug.setUpdateTime(System.currentTimeMillis());
bugMapper.updateByPrimaryKeySelective(bug);
@ -712,9 +748,6 @@ public class BugService {
*/
File tempFileDir = new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource(StringUtils.EMPTY)).getPath() + File.separator + "tmp"
+ File.separator);
if (!tempFileDir.exists()) {
tempFileDir.mkdirs();
}
// 同步删除附件集合
List<SyncAttachmentToPlatformRequest> removeAttachments = removeAttachment(request, platformBug, currentUser, platformName);
// 同步上传附件集合
@ -807,6 +840,7 @@ public class BugService {
bugAttachment.setFileId(IDGenerator.nextStr());
bugAttachment.setFileName(file.getOriginalFilename());
bugAttachment.setSize(file.getSize());
bugAttachment.setSource(BugAttachmentSourceType.ATTACHMENT.name());
bugAttachment.setCreateTime(System.currentTimeMillis());
bugAttachment.setCreateUser(currentUser);
addFiles.add(bugAttachment);
@ -908,22 +942,21 @@ public class BugService {
List<BugRelateCaseCountDTO> relationCaseCount = extBugRelateCaseMapper.countRelationCases(ids);
Map<String, Integer> countMap = relationCaseCount.stream().collect(Collectors.toMap(BugRelateCaseCountDTO::getBugId, BugRelateCaseCountDTO::getRelationCaseCount));
bugs.forEach(bug -> {
bug.setRelationCaseCount(countMap.get(bug.getId()));
bug.setRelationCaseCount(countMap.get(bug.getId()) == null ? 0 : countMap.get(bug.getId()));
bug.setCreateUserName(userMap.get(bug.getCreateUser()));
bug.setUpdateUser(userMap.get(bug.getUpdateUser()));
bug.setDeleteUser(userMap.get(bug.getDeleteUser()));
bug.setHandleUserName(userMap.get(bug.getHandleUser()));
bug.setUpdateUserName(userMap.get(bug.getUpdateUser()));
bug.setDeleteUserName(userMap.get(bug.getDeleteUser()));
});
return bugs;
}
/**
* 处理自定义字段及状态字段
* 处理自定义字段
*
* @param bugs 缺陷集合
* @return 缺陷DTO集合
*/
private List<BugDTO> handleCustomFieldsAndStatus(List<BugDTO> bugs, String projectId) {
private List<BugDTO> handleCustomField(List<BugDTO> bugs, String projectId) {
List<String> ids = bugs.stream().map(BugDTO::getId).toList();
List<BugCustomFieldDTO> customFields = extBugCustomFieldMapper.getBugAllCustomFields(ids, projectId);
Map<String, List<BugCustomFieldDTO>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(BugCustomFieldDTO::getBugId));
@ -940,6 +973,7 @@ public class BugService {
* @return 平台字段集合
*/
public List<PlatformCustomFieldItemDTO> transferCustomToPlatformField(String templateId, List<BugCustomFieldDTO> customFields, boolean noApiFilter) {
List<BugCustomFieldDTO> platformCustomFields = new ArrayList<>(customFields);
if (!noApiFilter) {
// 过滤出API映射的字段
List<TemplateCustomField> systemCustomsFields = baseTemplateCustomFieldService.getByTemplateId(templateId);
@ -947,11 +981,11 @@ public class BugService {
if (CollectionUtils.isNotEmpty(systemCustomsFields)) {
systemCustomFieldApiMap = systemCustomsFields.stream().collect(Collectors.toMap(TemplateCustomField::getFieldId, f -> Optional.ofNullable(f.getApiFieldId()).orElse(StringUtils.EMPTY)));
// 移除除状态, 处理人以外的所有非API映射的字段
customFields.removeIf(field -> systemCustomFieldApiMap.containsKey(field.getId()) && StringUtil.isBlank(systemCustomFieldApiMap.get(field.getId())));
platformCustomFields.removeIf(field -> systemCustomFieldApiMap.containsKey(field.getId()) && StringUtil.isBlank(systemCustomFieldApiMap.get(field.getId())));
} else {
systemCustomFieldApiMap = new HashMap<>();
}
return customFields.stream().map(field -> {
return platformCustomFields.stream().map(field -> {
PlatformCustomFieldItemDTO platformCustomFieldItem = new PlatformCustomFieldItemDTO();
platformCustomFieldItem.setName(field.getName());
platformCustomFieldItem.setCustomData(systemCustomFieldApiMap.containsKey(field.getId()) ? systemCustomFieldApiMap.get(field.getId()) : field.getId());
@ -961,7 +995,7 @@ public class BugService {
}).collect(Collectors.toList());
} else {
// 平台默认模板, 处理所有自定义字段
return customFields.stream().map(field -> {
return platformCustomFields.stream().map(field -> {
PlatformCustomFieldItemDTO platformCustomFieldItem = new PlatformCustomFieldItemDTO();
platformCustomFieldItem.setName(field.getName());
platformCustomFieldItem.setCustomData(field.getId());
@ -1080,8 +1114,8 @@ public class BugService {
* @return 导出对象
* @throws Exception 异常
*/
public ResponseEntity<byte[]> export(BugExportRequest request, String currentUser) throws Exception {
List<BugDTO> bugs = this.getExportDataByBatchRequest(request, currentUser);
public ResponseEntity<byte[]> export(BugExportRequest request) throws Exception {
List<BugDTO> bugs = this.getExportDataByBatchRequest(request);
if (CollectionUtils.isEmpty(bugs)) {
throw new MSException(Translator.get("no_bug_select"));
}
@ -1109,13 +1143,12 @@ public class BugService {
* @param request 批量操作参数
* @return 缺陷集合
*/
private List<BugDTO> getExportDataByBatchRequest(BugBatchRequest request, String currentUser) {
private List<BugDTO> getExportDataByBatchRequest(BugBatchRequest request) {
if (request.isSelectAll()) {
// 全选{根据查询条件查询所有数据, 排除取消勾选的数据}
BugPageRequest bugPageRequest = new BugPageRequest();
BeanUtils.copyBean(bugPageRequest, request);
bugPageRequest.setUseTrash(false);
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(bugPageRequest, currentUser);
List<BugDTO> allBugs = extBugMapper.list(bugPageRequest);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
allBugs.removeIf(bug -> request.getExcludeIds().contains(bug.getId()));
@ -1135,13 +1168,11 @@ public class BugService {
* @param request 批量操作参数
* @return 缺陷集合
*/
private List<String> getBatchIdsByRequest(BugBatchRequest request, String currentUser) {
private List<String> getBatchIdsByRequest(BugBatchRequest request) {
if (request.isSelectAll()) {
// 全选{根据查询条件查询所有数据, 排除取消勾选的数据}
BugPageRequest bugPageRequest = new BugPageRequest();
BeanUtils.copyBean(bugPageRequest, request);
bugPageRequest.setUseTrash(false);
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(bugPageRequest, currentUser);
List<String> ids = extBugMapper.getIdsByPageRequest(bugPageRequest);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
ids.removeIf(id -> request.getExcludeIds().contains(id));
@ -1158,4 +1189,94 @@ public class BugService {
return request.getSelectIds();
}
}
}
/**
* 获取表头处理人选项
* @param projectId 项目ID
* @return 处理人选项集合
*/
public List<SelectOption> getHeaderHandlerOption(String projectId) {
String platformName = projectApplicationService.getPlatformName(projectId);
// 需要校验服务集成是否开启
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
if (StringUtils.equals(platformName, BugPlatform.LOCAL.getName()) || serviceIntegration == null) {
// Local处理人
return getLocalHandlerOption(projectId);
} else {
// 第三方平台(Local处理人 && 平台处理人)
List<SelectOption> localHandlerOption = getLocalHandlerOption(projectId);
// 获取插件中自定义的注入字段(处理人)
Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(),
new String(serviceIntegration.getConfiguration()));
List<SelectOption> platformHandlerOption = new ArrayList<>();
List<BugTemplateInjectField> platformInjectFields = getPlatformInjectFields(projectId);
for (BugTemplateInjectField injectField : platformInjectFields) {
if (StringUtils.equals(injectField.getKey(), "assignee")) {
GetOptionRequest request = new GetOptionRequest();
request.setOptionMethod(injectField.getOptionMethod());
request.setProjectConfig(projectApplicationService.getProjectBugThirdPartConfig(projectId));
platformHandlerOption = platform.getFormOptions(request);
}
}
return ListUtils.union(localHandlerOption, platformHandlerOption);
}
}
/**
* 项目成员选项(处理人)
* @param projectId 项目ID
* @return 处理人选项集合
*/
private List<SelectOption> getLocalHandlerOption(String projectId) {
ProjectMemberRequest request = new ProjectMemberRequest();
request.setProjectId(projectId);
List<ProjectUserDTO> projectMembers = projectMemberService.listMember(request);
return projectMembers.stream().map(user -> {
SelectOption option = new SelectOption();
option.setText(user.getName());
option.setValue(user.getId());
return option;
}).toList();
}
/**
* 获取平台注入的字段
* @param projectId 项目ID
* @return 注入的字段集合
*/
private List<BugTemplateInjectField> getPlatformInjectFields(String projectId) {
// 获取插件中自定义的注入字段(处理人)
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
AbstractPlatformPlugin platformPlugin = (AbstractPlatformPlugin) pluginLoadService.getMsPluginManager().getPlugin(serviceIntegration.getPluginId()).getPlugin();
Object scriptContent = pluginLoadService.getPluginScriptContent(serviceIntegration.getPluginId(), platformPlugin.getProjectBugTemplateInjectField());
String injectFields = JSON.toJSONString(((Map<?, ?>) scriptContent).get("injectFields"));
return JSON.parseArray(injectFields, BugTemplateInjectField.class);
}
/**
* 获取表头自定义字段
* @param projectId 项目ID
* @return 自定义字段集合
*/
public List<TemplateCustomFieldDTO> getHeaderCustomFields(String projectId) {
List<TemplateCustomFieldDTO> headerCustomFields = new ArrayList<>();
// 本地模板
List<Template> templates = projectTemplateService.getTemplates(projectId, TemplateScene.BUG.name());
templates.forEach(template -> headerCustomFields.addAll(baseTemplateService.getTemplateDTO(template).getCustomFields()));
// 第三方平台模板
TemplateDTO pluginDefaultTemplate = getPluginBugDefaultTemplate(projectId, true);
if (pluginDefaultTemplate != null) {
headerCustomFields.addAll(pluginDefaultTemplate.getCustomFields());
}
// 重复的自定义字段去重
return headerCustomFields.stream().distinct().toList();
}
private Bug checkById(String bugId) {
Bug bug = bugMapper.selectByPrimaryKey(bugId);
if (bug == null) {
throw new MSException(BUG_NOT_EXIST);
}
return bug;
}
}

View File

@ -7,6 +7,7 @@ import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.sdk.constants.TemplateScene;
import io.metersphere.system.service.BaseStatusFlowSettingService;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -22,6 +23,26 @@ public class BugStatusService {
@Resource
private BaseStatusFlowSettingService baseStatusFlowSettingService;
/**
* 获取表头缺陷状态选项
* @param projectId 项目ID
* @return 选项集合
*/
public List<SelectOption> getHeaderStatusOption(String projectId) {
String platformName = projectApplicationService.getPlatformName(projectId);
if (StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
// Local状态流
return getAllLocalStatusOptions(projectId);
} else {
// 第三方平台(Local状态流 && 第三方平台状态流)
List<SelectOption> localStatusOption = getAllLocalStatusOptions(projectId);
Platform platform = projectApplicationService.getPlatform(projectId, true);
String projectConfig = projectApplicationService.getProjectBugThirdPartConfig(projectId);
List<SelectOption> platformStatusOption = platform.getStatusTransitions(projectConfig, null);
return ListUtils.union(localStatusOption, platformStatusOption);
}
}
/**
* 获取缺陷下一批状态流转选项
* @param projectId 项目ID
@ -52,4 +73,13 @@ public class BugStatusService {
public List<SelectOption> getToStatusItemOptionOnLocal(String projectId, String fromStatusId) {
return baseStatusFlowSettingService.getStatusTransitions(projectId, TemplateScene.BUG.name(), fromStatusId);
}
/**
* 获取Local状态流选项
* @param projectId 项目ID
* @return 状态流选项
*/
public List<SelectOption> getAllLocalStatusOptions(String projectId) {
return baseStatusFlowSettingService.getAllStatusOption(projectId, TemplateScene.BUG.name());
}
}

View File

@ -3,6 +3,7 @@ package io.metersphere.bug.service;
import io.metersphere.bug.domain.BugLocalAttachment;
import io.metersphere.bug.domain.BugLocalAttachmentExample;
import io.metersphere.bug.dto.response.BugFileDTO;
import io.metersphere.bug.enums.BugAttachmentSourceType;
import io.metersphere.bug.mapper.BugLocalAttachmentMapper;
import io.metersphere.plugin.platform.dto.PlatformAttachment;
import io.metersphere.plugin.platform.dto.request.SyncAttachmentToPlatformRequest;
@ -77,7 +78,7 @@ public class BugSyncExtraService {
* @param projectId 项目ID
*/
public void setSyncErrorMsg(String projectId, String errorMsg) {
stringRedisTemplate.opsForValue().set(SYNC_THIRD_PARTY_ISSUES_ERROR_KEY + ":" + projectId, errorMsg, 30L, TimeUnit.SECONDS);
stringRedisTemplate.opsForValue().set(SYNC_THIRD_PARTY_ISSUES_ERROR_KEY + ":" + projectId, errorMsg, 60L, TimeUnit.SECONDS);
}
/**
@ -169,6 +170,7 @@ public class BugSyncExtraService {
localAttachment.setSize((long) bytes.length);
localAttachment.setCreateTime(System.currentTimeMillis());
localAttachment.setCreateUser("admin");
localAttachment.setSource(BugAttachmentSourceType.ATTACHMENT.name());
bugLocalAttachmentMapper.insert(localAttachment);
});
} catch (Exception e) {

View File

@ -2,6 +2,7 @@ package io.metersphere.bug.service;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.domain.BugExample;
import io.metersphere.bug.dto.BugSyncResult;
import io.metersphere.bug.dto.request.BugSyncRequest;
import io.metersphere.bug.enums.BugPlatform;
import io.metersphere.bug.mapper.BugMapper;
@ -107,6 +108,28 @@ public class BugSyncService {
}
}
/**
* 校验同步状态
* @param projectId 项目ID
* @return 同步结果
*/
public BugSyncResult checkSyncStatus(String projectId) {
BugSyncResult result = BugSyncResult.builder().complete(Boolean.FALSE).msg(StringUtils.EMPTY).build();
String syncValue = bugSyncExtraService.getSyncKey(projectId);
// redis-key 存在, 说明正在同步
if (StringUtils.isNotEmpty(syncValue)) {
return result;
}
// 否则, 项目同步已结束, 设置同步Msg
result.setComplete(Boolean.TRUE);
result.setMsg(bugSyncExtraService.getSyncErrorMsg(projectId));
if (StringUtils.isNotEmpty(result.getMsg())) {
// 清空同步异常信息
bugSyncExtraService.deleteSyncErrorMsg(projectId);
}
return result;
}
/**
* 定时任务同步缺陷(存量)
*/

View File

@ -70,11 +70,14 @@
</javaClientGenerator>
<!--要生成的数据库表 -->
<table tableName="bug" />
<table tableName="bug">
<columnOverride column="tags" javaType="java.util.List&lt;String&gt;" jdbcType="VARCHAR"
typeHandler="io.metersphere.handler.ListTypeHandler"/>
</table>
<!-- <table tableName="bug_content" />-->
<!-- <table tableName="bug_follower" />-->
<!-- <table tableName="bug_comment" />-->
<table tableName="bug_local_attachment" />
<!-- <table tableName="bug_local_attachment" />-->
<!-- <table tableName="bug_custom_field" />-->
<!-- <table tableName="bug_relation_case" />-->
<!-- <table tableName="bug_history" />-->

View File

@ -9,16 +9,16 @@
"name": "permission.bug.name",
"permissions": [
{
"id": "BUG:READ"
"id": "PROJECT_BUG:READ"
},
{
"id": "BUG:READ+ADD"
"id": "PROJECT_BUG:READ+ADD"
},
{
"id": "BUG:READ+UPDATE"
"id": "PROJECT_BUG:READ+UPDATE"
},
{
"id": "BUG:READ+DELETE"
"id": "PROJECT_BUG:READ+DELETE"
},
{
"id": "PROJECT_BUG:READ+EXPORT"

View File

@ -0,0 +1,12 @@
package io.metersphere.bug.config;
import io.metersphere.provider.BaseAssociateCaseProvider;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
@TestConfiguration
public class BugProviderConfiguration {
@MockBean
BaseAssociateCaseProvider baseAssociateCaseProvider;
}

View File

@ -19,6 +19,7 @@ import io.metersphere.project.dto.ProjectTemplateOptionDTO;
import io.metersphere.project.mapper.FileAssociationMapper;
import io.metersphere.project.mapper.FileMetadataMapper;
import io.metersphere.project.mapper.ProjectApplicationMapper;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.project.service.FileService;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants;
@ -37,7 +38,6 @@ import io.metersphere.system.mapper.CustomFieldMapper;
import io.metersphere.system.mapper.ServiceIntegrationMapper;
import io.metersphere.system.service.PluginService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.CustomFieldUtils;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
@ -65,6 +65,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BugControllerTests extends BaseTest {
public static final String BUG_HEADER_CUSTOM_FIELD = "/bug/header/custom-field";
public static final String BUG_HEADER_STATUS_OPTION = "/bug/header/status-option";
public static final String BUG_HEADER_HANDLER_OPTION = "/bug/header/handler-option";
public static final String BUG_PAGE = "/bug/page";
public static final String BUG_ADD = "/bug/add";
public static final String BUG_UPDATE = "/bug/update";
@ -77,6 +80,7 @@ public class BugControllerTests extends BaseTest {
public static final String BUG_UN_FOLLOW = "/bug/unfollow";
public static final String BUG_SYNC = "/bug/sync";
public static final String BUG_SYNC_ALL = "/bug/sync/all";
public static final String BUG_SYNC_CHECK = "/bug/sync/check";
public static final String BUG_EXPORT_COLUMNS = "/bug/export/columns/%s";
public static final String BUG_EXPORT = "/bug/export";
@ -101,6 +105,8 @@ public class BugControllerTests extends BaseTest {
@Resource
private BugService bugService;
@Resource
private ProjectMapper projectMapper;
@Resource
private ServiceIntegrationMapper serviceIntegrationMapper;
@Resource
private ProjectApplicationMapper projectApplicationMapper;
@ -121,6 +127,10 @@ public class BugControllerTests extends BaseTest {
@Test
@Order(1)
void testBugPageSuccess() throws Exception {
// 表头字段, 状态选项, 处理人选项
this.requestGetWithOk(BUG_HEADER_CUSTOM_FIELD + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_STATUS_OPTION + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
BugPageRequest bugRequest = new BugPageRequest();
bugRequest.setCurrent(1);
bugRequest.setPageSize(10);
@ -187,45 +197,6 @@ public class BugControllerTests extends BaseTest {
bugPageRequest.setFilter(filter);
bugPageRequest.setCombine(null);
this.requestPostWithOkAndReturn(BUG_PAGE, bugPageRequest);
// cover combine
bugPageRequest.setFilter(null);
Map<String, Object> combine = new HashMap<>();
List<Map<String, Object>> customs = new ArrayList<>();
Map<String, Object> custom = new HashMap<>();
custom.put("id", "test_field");
custom.put("operator", "in");
custom.put("type", "multipleMember");
custom.put("value", StringUtils.EMPTY);
customs.add(custom);
Map<String, Object> currentUserCustom = new HashMap<>();
currentUserCustom.put("id", "test_field");
currentUserCustom.put("operator", "current user");
currentUserCustom.put("type", "multipleMember");
currentUserCustom.put("value", "current user");
customs.add(currentUserCustom);
combine.put("customs", customs);
bugPageRequest.setCombine(combine);
this.requestPostWithOkAndReturn(BUG_PAGE, bugPageRequest);
custom.put("id", "custom-field");
custom.put("operator", "like");
custom.put("type", "textarea");
custom.put("value", "oasis");
customs.clear();
customs.add(custom);
combine.put("customs", customs);
bugPageRequest.setCombine(combine);
this.requestPostWithOkAndReturn(BUG_PAGE, bugPageRequest);
// cover combine current user
custom.clear();
custom.put("operator", "current user");
custom.put("value", "current user");
combine.put("handleUser", custom);
currentUserCustom.clear();
currentUserCustom.put("operator", "in");
currentUserCustom.put("value", List.of("admin"));
combine.put("createUser", currentUserCustom);
bugPageRequest.setCombine(combine);
this.requestPostWithOkAndReturn(BUG_PAGE, bugPageRequest);
}
@Test
@ -369,12 +340,12 @@ public class BugControllerTests extends BaseTest {
request.setSelectAll(true);
request.setSelectIds(List.of("test"));
// TAG追加
request.setTag(JSON.toJSONString(List.of("TAG", "TEST_TAG")));
request.setTags(List.of("TAG", "TEST_TAG"));
request.setAppend(true);
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
// TAG覆盖
request.setExcludeIds(List.of("default-bug-id-tapd1"));
request.setTag(JSON.toJSONString(List.of("A", "B")));
request.setTags(List.of("A", "B"));
request.setAppend(false);
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
// 勾选部分
@ -391,7 +362,7 @@ public class BugControllerTests extends BaseTest {
// 全选, 空数据
request.setSelectAll(true);
request.setSelectIds(List.of("test"));
request.setTag(JSON.toJSONString(List.of("TAG", "TEST_TAG")));
request.setTags(List.of("TAG", "TEST_TAG"));
request.setAppend(true);
this.requestPost(BUG_BATCH_UPDATE, request, status().is5xxServerError());
// 取消全选, 空数据
@ -510,12 +481,6 @@ public class BugControllerTests extends BaseTest {
@Test
@Order(95)
void coverUtilsTests() {
CustomFieldUtils.appendToMultipleCustomField(null, "test");
}
@Test
@Order(96)
void coverPlatformTemplateTests() throws Exception{
// 覆盖同步缺陷(Local)
this.requestGetWithOk(BUG_SYNC + "/default-project-for-not-integration");
@ -538,6 +503,8 @@ public class BugControllerTests extends BaseTest {
record.setEnable(false);
serviceIntegrationMapper.updateByPrimaryKeySelective(record);
this.requestPost(BUG_TEMPLATE_DETAIL, request, status().is5xxServerError());
// 获取处理人选项(Local)
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
// 开启插件集成
record.setEnable(true);
serviceIntegrationMapper.updateByPrimaryKeySelective(record);
@ -557,14 +524,12 @@ public class BugControllerTests extends BaseTest {
@Test
@Order(96)
void coverPlatformBugSyncTests() throws Exception {
// 添加一条需要同步删除的缺陷
BugEditRequest deleteRequest = buildJiraBugRequest(false);
MultiValueMap<String, Object> deleteParam = getDefaultMultiPartParam(deleteRequest, null);
this.requestMultipartWithOkAndReturn(BUG_ADD, deleteParam);
Bug record = new Bug();
record.setId(getAddJiraBug().getId());
record.setPlatformBugId("Tapd-XXX");
bugMapper.updateByPrimaryKeySelective(record);
// 表头字段, 状态选项, 处理人选项 (非Local平台)
this.requestGetWithOk(BUG_HEADER_CUSTOM_FIELD + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_STATUS_OPTION + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
// 同步删除缺陷(default-bug-id-jira-sync)
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 添加Jira缺陷
@ -574,50 +539,51 @@ public class BugControllerTests extends BaseTest {
MultiValueMap<String, Object> addParam = getDefaultMultiPartParam(addRequest, file);
this.requestMultipartWithOkAndReturn(BUG_ADD, addParam);
// 添加没有附件的Jira缺陷
addRequest.setLinkFileIds(null);
MultiValueMap<String, Object> addParam2 = getDefaultMultiPartParam(addRequest, null);
this.requestMultipartWithOkAndReturn(BUG_ADD, addParam2);
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 添加使用Jira默认模板的缺陷
addRequest.setTemplateId("jira");
MultiValueMap<String, Object> addParam3 = getDefaultMultiPartParam(addRequest, null);
this.requestMultipart(BUG_ADD, addParam3).andExpect(status().is5xxServerError());
// 更新Jira缺陷
BugEditRequest updateRequest = buildJiraBugRequest(true);
updateRequest.setUnLinkRefIds(List.of(getAddJiraAssociateFile().getId()));
updateRequest.setDeleteLocalFileIds(List.of(getAddJiraLocalFile().getFileId()));
MultiValueMap<String, Object> updateParma = getDefaultMultiPartParam(updateRequest, null);
this.requestMultipartWithOkAndReturn(BUG_UPDATE, updateParma);
// 删除Jira缺陷
this.requestGet(BUG_DELETE + "/" + updateRequest.getId(), status().isOk());
// 添加使用Jira默认模板的缺陷
addRequest.setTemplateId("jira");
MultiValueMap<String, Object> addParam3 = getDefaultMultiPartParam(addRequest, null);
this.requestMultipart(BUG_ADD, addParam3).andExpect(status().is5xxServerError());
// 同步Jira存量缺陷(存量数据为空)
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 添加没有附件的Jira缺陷
addRequest.setLinkFileIds(null);
addRequest.setTemplateId("default-bug-template-id");
MultiValueMap<String, Object> addParam2 = getDefaultMultiPartParam(addRequest, null);
this.requestMultipartWithOkAndReturn(BUG_ADD, addParam2);
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 覆盖Redis-Key还未删除的情况
this.requestGetWithOk(BUG_SYNC_CHECK + "/default-project-for-bug");
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 更新没有附件的缺陷
BugEditRequest updateRequest2 = buildJiraBugRequest(true);
updateRequest2.setLinkFileIds(List.of("default-bug-file-id-1"));
MultiValueMap<String, Object> updateParam2 = getDefaultMultiPartParam(updateRequest2, file);
this.requestMultipartWithOkAndReturn(BUG_UPDATE, updateParam2);
// 同步方法为异步, 所以换成手动调用
BugExample example = new BugExample();
example.createCriteria().andIdEqualTo(updateRequest2.getId());
List<Bug> remainBugs = bugMapper.selectByExample(example);
Project defaultProject = projectMapper.selectByPrimaryKey("default-project-for-bug");
// 同步第一次
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
bugService.syncPlatformBugs(remainBugs, defaultProject);
// 同步第二次
renameLocalFile(updateRequest2.getId()); // 重命名后, 同步时会删除本地文件
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
bugService.syncPlatformBugs(remainBugs, defaultProject);
// 同步第三次
deleteLocalFile(updateRequest2.getId()); // 手动删除关联的文件, 重新同步时会下载平台附件
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 删除唯一条平台缺陷
BugBatchRequest batchRequest = new BugBatchRequest();
batchRequest.setProjectId("default-project-for-bug");
batchRequest.setSelectAll(true);
batchRequest.setSelectIds(List.of("test"));
batchRequest.setExcludeIds(List.of("default-bug-id-tapd1"));
this.requestPost(BUG_BATCH_DELETE, batchRequest, status().isOk());
// 同步Jira存量缺陷(存量数据为空)
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
bugService.syncPlatformBugs(remainBugs, defaultProject);
// 集成配置为空
addRequest.setProjectId("default-project-for-not-integration");
@ -671,7 +637,7 @@ public class BugControllerTests extends BaseTest {
BugExportRequest request = new BugExportRequest();
request.setSelectAll(true);
request.setExcludeIds(List.of("test-id"));
bugService.export(request, "admin");
bugService.export(request);
}
@Test
@ -693,6 +659,21 @@ public class BugControllerTests extends BaseTest {
this.requestMultipart(BUG_ADD, addParam).andExpect(status().is5xxServerError());
// 获取禅道模板(删除默认项目模板)
bugService.attachTemplateStatusField(null, null, null, null);
// 获取处理人选项
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
// 批量删除
BugBatchRequest request = new BugBatchRequest();
request.setProjectId("default-project-for-bug");
request.setSelectAll(false);
request.setSelectIds(List.of("default-bug-id"));
this.requestPost(BUG_BATCH_DELETE, request, status().is5xxServerError());
// check一下同步状态
bugSyncExtraService.deleteSyncKey("default-project-for-bug");
bugSyncExtraService.setSyncErrorMsg("default-project-for-bug", "sync error!");
bugSyncService.checkSyncStatus("default-project-for-bug");
// 覆盖空Msg
bugSyncService.checkSyncStatus("default-project-for-bug");
}
/**
@ -701,7 +682,7 @@ public class BugControllerTests extends BaseTest {
*/
private Map<String, List<String>> buildRequestFilter() {
Map<String, List<String>> filter = new HashMap<>();
filter.put("custom_multiple_test_field", List.of("default", "default1"));
filter.put("custom_multiple_test_field", List.of("default", "default-1"));
return filter;
}
@ -715,8 +696,8 @@ public class BugControllerTests extends BaseTest {
Map<String, Object> custom = new HashMap<>();
custom.put("id", "test_field");
custom.put("operator", "in");
custom.put("type", "multipleSelect");
custom.put("value", JSON.toJSONString(List.of("default", "default1")));
custom.put("type", "array");
custom.put("value", List.of("default", "default-1"));
customs.add(custom);
combine.put("customs", customs);
return combine;
@ -838,7 +819,7 @@ public class BugControllerTests extends BaseTest {
}
/**
* 添加Jira插件供测试使用
* 添加禅道插件供测试使用
* @throws Exception 异常
*/
public void addZentaoPlugin() throws Exception {

View File

@ -1,14 +1,21 @@
package io.metersphere.bug.controller;
import io.metersphere.bug.dto.request.BugRelateCaseModuleRequest;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.service.BugRelateCaseCommonService;
import io.metersphere.provider.BaseAssociateCaseProvider;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.sdk.constants.UserRoleType;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
import org.mockito.Mockito;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
@ -16,6 +23,8 @@ import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -24,20 +33,71 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BugRelateCaseControllerTests extends BaseTest {
public static final String BUG_CASE_RELATE_PAGE = "/bug/relate/case/page";
public static final String BUG_CASE_UN_RELATE = "/bug/relate/case/un-relate";
public static final String BUG_CASE_RELATE_CHECK = "/bug/relate/case/check-permission";
@Resource
BaseAssociateCaseProvider functionalCaseProvider;
@Resource
BugRelateCaseCommonService bugRelateCaseCommonService;
public static final String BUG_CASE_UN_RELATE_PAGE = "/bug/case/un-relate/page";
public static final String BUG_CASE_UN_RELATE_MODULE_TREE = "/bug/case/un-relate/module/tree";
public static final String BUG_CASE_UN_RELATE_MODULE_COUNT = "/bug/case/un-relate/module/count";
public static final String BUG_CASE_RELATE = "/bug/case/relate";
public static final String BUG_CASE_PAGE = "/bug/case/page";
public static final String BUG_CASE_UN_RELATE = "/bug/case/un-relate";
public static final String BUG_CASE_CHECK = "/bug/case/check-permission";
@Test
@Order(0)
@Sql(scripts = {"/dml/init_bug_case.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
void testBugUnRelateCasePage() throws Exception {
TestCasePageProviderRequest request = new TestCasePageProviderRequest();
request.setCurrent(1);
request.setPageSize(10);
request.setProjectId("default-project-for-bug");
request.setVersionId("default_bug_version");
request.setSourceId("default-relate-bug-id'");
request.setSourceType("FUNCTIONAL");
this.requestPost(BUG_CASE_UN_RELATE_PAGE, request, status().is5xxServerError());
}
@Test
@Order(1)
void testBugUnRelateCaseModule() throws Exception {
BugRelateCaseModuleRequest request = new BugRelateCaseModuleRequest();
request.setProjectId("default-project-for-bug");
request.setVersionId("default_bug_version");
request.setSourceId("default-relate-bug-id'");
request.setSourceType("FUNCTIONAL");
this.requestPostWithOk(BUG_CASE_UN_RELATE_MODULE_TREE, request);
this.requestPostWithOk(BUG_CASE_UN_RELATE_MODULE_COUNT, request);
}
@Test
@Order(2)
void testBugRelateCase() throws Exception {
AssociateOtherCaseRequest request = new AssociateOtherCaseRequest();
request.setSelectAll(true);
request.setExcludeIds(List.of("bug_relate_case-2", "bug_relate_case-3"));
request.setProjectId("default-project-for-bug");
request.setVersionId("default_bug_version");
request.setSourceId("default-relate-bug-id");
request.setSourceType("FUNCTIONAL");
Mockito.when(functionalCaseProvider.getRelatedIdsByParam(request, false)).thenReturn(Collections.emptyList());
this.requestPostWithOk(BUG_CASE_RELATE, request);
request.setExcludeIds(null);
Mockito.when(functionalCaseProvider.getRelatedIdsByParam(request, false)).thenReturn(List.of("bug_relate_case-2", "bug_relate_case-3"));
this.requestPostWithOk(BUG_CASE_RELATE, request);
}
@Test
@Order(3)
void testBugRelatePageSuccess() throws Exception {
BugRelatedCasePageRequest request = new BugRelatedCasePageRequest();
request.setCurrent(1);
request.setPageSize(10);
request.setBugId("default-relate-bug-id");
request.setKeyword("first");
MvcResult mvcResult = this.requestPostWithOkAndReturn(BUG_CASE_RELATE_PAGE, request);
MvcResult mvcResult = this.requestPostWithOkAndReturn(BUG_CASE_PAGE, request);
// 获取返回值
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
@ -56,14 +116,14 @@ public class BugRelateCaseControllerTests extends BaseTest {
}
@Test
@Order(1)
@Order(4)
void testBugRelatePageEmpty() throws Exception {
BugRelatedCasePageRequest request = new BugRelatedCasePageRequest();
request.setCurrent(1);
request.setPageSize(10);
request.setBugId("default-relate-bug-id");
request.setKeyword("keyword");
MvcResult mvcResult = this.requestPostWithOkAndReturn(BUG_CASE_RELATE_PAGE, request);
MvcResult mvcResult = this.requestPostWithOkAndReturn(BUG_CASE_PAGE, request);
// 获取返回值
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
@ -79,46 +139,53 @@ public class BugRelateCaseControllerTests extends BaseTest {
}
@Test
@Order(2)
@Order(5)
void testBugRelatePageError() throws Exception {
// 页码有误
BugRelatedCasePageRequest request = new BugRelatedCasePageRequest();
request.setCurrent(0);
request.setPageSize(10);
this.requestPost(BUG_CASE_RELATE_PAGE, request, status().isBadRequest());
this.requestPost(BUG_CASE_PAGE, request, status().isBadRequest());
// 页数有误
request = new BugRelatedCasePageRequest();
request.setCurrent(1);
request.setPageSize(1);
this.requestPost(BUG_CASE_RELATE_PAGE, request, status().isBadRequest());
this.requestPost(BUG_CASE_PAGE, request, status().isBadRequest());
}
@Test
@Order(3)
@Order(6)
void testBugUnRelateSuccess() throws Exception {
this.requestGetWithOk(BUG_CASE_UN_RELATE + "/bug-relate-case-default-id");
this.requestGetWithOk(BUG_CASE_UN_RELATE + "/bug-relate-case-default-id-1");
}
@Test
@Order(4)
@Order(7)
void testBugUnRelateError() throws Exception {
this.requestGet(BUG_CASE_UN_RELATE + "/bug-relate-case-default-id-x", status().is5xxServerError());
}
@Test
@Order(5)
@Order(8)
void testBugRelateCheckPermissionError() throws Exception {
// 非功能用例类型参数
this.requestGet(BUG_CASE_RELATE_CHECK + "/100001100001/API", status().is5xxServerError());
this.requestGet(BUG_CASE_CHECK + "/100001100001/API", status().is5xxServerError());
}
@Test
@Order(6)
@Order(9)
void testBugRelateCheckPermissionSuccess() throws Exception {
// 默认项目ID且登录用户为admin
this.requestGet(BUG_CASE_RELATE_CHECK + "/100001100001/FUNCTIONAL", status().isOk()).andReturn();
this.requestGet(BUG_CASE_CHECK + "/100001100001/FUNCTIONAL", status().isOk()).andReturn();
// 切换登录用户为PROJECT, 权限校验失败
this.requestGetWithNoAdmin(BUG_CASE_RELATE_CHECK + "/default-project-for-bug/FUNCTIONAL", UserRoleType.PROJECT.name(), status().is5xxServerError()).andReturn();
this.requestGetWithNoAdmin(BUG_CASE_CHECK + "/default-project-for-bug/FUNCTIONAL", UserRoleType.PROJECT.name(), status().is5xxServerError()).andReturn();
}
@Test
@Order(10)
void coverTest() {
bugRelateCaseCommonService.refreshPos(null);
bugRelateCaseCommonService.updatePos(null, 1L);
}
}

View File

@ -0,0 +1,108 @@
package io.metersphere.bug.controller;
import io.metersphere.bug.dto.request.BugBatchRequest;
import io.metersphere.bug.dto.request.BugPageRequest;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.utils.Pager;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BugTrashControllerTests extends BaseTest {
public static final String BUG_TRASH_PAGE = "/bug/trash/page";
public static final String BUG_TRASH_RECOVER = "/bug/trash/recover";
public static final String BUG_TRASH_DELETE = "/bug/trash/delete";
public static final String BUG_TRASH_BATCH_RECOVER = "/bug/trash/batch-recover";
public static final String BUG_TRASH_BATCH_DELETE = "/bug/trash/batch-delete";
@Test
@Order(0)
@Sql(scripts = {"/dml/init_bug_trash.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
void testTrashPage() throws Exception{
BugPageRequest bugRequest = new BugPageRequest();
bugRequest.setCurrent(1);
bugRequest.setPageSize(10);
bugRequest.setProjectId("bug-trash-project");
MvcResult mvcResult = this.requestPostWithOkAndReturn(BUG_TRASH_PAGE, bugRequest);
// 获取返回值
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
// 返回请求正常
Assertions.assertNotNull(resultHolder);
Pager<?> pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
// 返回值不为空
Assertions.assertNotNull(pageData);
// 返回值的页码和当前页码相同
Assertions.assertEquals(pageData.getCurrent(), bugRequest.getCurrent());
// 返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= bugRequest.getPageSize());
}
@Test
@Order(1)
void testRecoverOrDeleteError() throws Exception {
// 恢复不存在的缺陷
this.requestGet(BUG_TRASH_RECOVER + "/trash-bug-not-exist", status().is5xxServerError());
// 恢复非Local缺陷
this.requestGet(BUG_TRASH_RECOVER + "/trash-bug-5", status().is5xxServerError());
// 删除非Local缺陷
this.requestGet(BUG_TRASH_DELETE + "/trash-bug-5", status().is5xxServerError());
}
@Test
@Order(1)
void testRecoverOrDeleteSuccess() throws Exception {
// 恢复Local缺陷
this.requestGetWithOk(BUG_TRASH_RECOVER + "/trash-bug-1");
// 删除Local缺陷
this.requestGetWithOk(BUG_TRASH_DELETE + "/trash-bug-2");
}
@Test
@Order(2)
void testBatchError() throws Exception {
// 全选, 恢复所有
BugBatchRequest recoverRequest = new BugBatchRequest();
recoverRequest.setProjectId("bug-trash-project-not-exist");
recoverRequest.setSelectAll(true);
// 批量恢复Local缺陷
this.requestPost(BUG_TRASH_BATCH_RECOVER, recoverRequest, status().is5xxServerError());
// 勾选部分, 恢复部分
BugBatchRequest recoverRequest1 = new BugBatchRequest();
recoverRequest1.setSelectAll(false);
// 批量恢复Local缺陷
this.requestPost(BUG_TRASH_BATCH_RECOVER, recoverRequest1, status().is5xxServerError());
}
@Test
@Order(2)
void testBatchSuccess() throws Exception {
// 全选, 恢复所有
BugBatchRequest recoverRequest = new BugBatchRequest();
recoverRequest.setProjectId("bug-trash-project");
recoverRequest.setSelectAll(true);
recoverRequest.setExcludeIds(List.of("trash-bug-3", "trash-bug-5"));
// 批量恢复Local缺陷
this.requestPostWithOk(BUG_TRASH_BATCH_RECOVER, recoverRequest);
// 勾选部分, 恢复部分
BugBatchRequest deleteRequest = new BugBatchRequest();
deleteRequest.setSelectAll(false);
deleteRequest.setSelectIds(List.of("trash-bug-3"));
// 批量恢复Local缺陷
this.requestPostWithOk(BUG_TRASH_BATCH_DELETE, deleteRequest);
}
}

View File

@ -9,7 +9,7 @@ INSERT INTO user_role_relation (id, user_id, role_id, source_id, organization_id
(UUID(), 'admin', 'project_admin', 'default-project-for-bug', '100001', UNIX_TIMESTAMP() * 1000, 'admin');
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag, platform_bug_id, deleted) VALUES
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUES
('default-bug-id', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0),
('default-bug-id-tapd1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0),
('default-bug-id-tapd2', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-no-local', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0),
@ -56,7 +56,7 @@ INSERT INTO project_application (project_id, type, type_value) VALUES
('default-project-for-bug', 'BUG_SYNC_PLATFORM_KEY', 'jira'),
('default-project-for-not-integration', 'BUG_SYNC_PLATFORM_KEY', 'jira'),
('default-project-for-bug', 'BUG_SYNC_SYNC_ENABLE', 'true'),
('default-project-for-bug', 'CASE_ENABLE', 'false'),
('default-project-for-bug', 'CASE_RELATED_CASE_ENABLE', 'false'),
('default-project-for-bug', 'CASE_RELATED_DEMAND_PLATFORM_CONFIG', '{"jiraKey":"TES","jiraDemandTypeId":"10007"}'),
('default-project-for-bug', 'CASE_RELATED_PLATFORM_KEY', 'jira');

View File

@ -9,16 +9,24 @@ INSERT INTO project_version(id, project_id, name, description, status, latest,
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos,version_id, ref_id, last_execute_result,
deleted, public_case, latest, create_user,update_user, delete_user, create_time, update_time, delete_time) VALUES
('bug_relate_case', 100099, 'init_module', 'default-project-for-bug', 'default_template', 'first_case1', 'UN_REVIEWED', null, 'STEP',
10001, '111', '111', 'UN_EXECUTED', 0, 0, true, 'admin', 'admin', null, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, null),
10001, 'default_bug_version', '111', 'UN_EXECUTED', 0, 0, true, 'admin', 'admin', null, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, null),
('bug_relate_case-1', 100099, 'init_module', '100001100001', 'default_template', 'first_case2', 'UN_REVIEWED', null, 'STEP',
10001, '111', '111', 'UN_EXECUTED', 0, 0, true, 'admin', 'admin', null, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, null);
10001, 'default_bug_version', '111', 'UN_EXECUTED', 0, 0, true, 'admin', 'admin', null, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, null),
('bug_relate_case-2', 100099, 'init_module', 'default-project-for-bug', 'default_template', 'first_case2', 'UN_REVIEWED', null, 'STEP',
10001, 'default_bug_version', '111', 'UN_EXECUTED', 0, 0, true, 'admin', 'admin', null, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, null),
('bug_relate_case-3', 100099, 'init_module', 'default-project-for-bug', 'default_template', 'first_case2', 'UN_REVIEWED', null, 'STEP',
10001, 'default_bug_version', '111', 'UN_EXECUTED', 0, 0, true, 'admin', 'admin', null, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, null);
INSERT INTO functional_case_module(id, project_id, name, parent_id, pos, create_time, create_user, update_time, update_user)
VALUES ('init_module', 'default-project-for-bug', 'test_module_name', 'NONE', '1', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin');
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag, platform_bug_id, deleted) VALUES
('default-relate-bug-id', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0),
('default-relate-bug-id-1', 100001, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0);
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUES
('default-relate-bug-id', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0),
('default-relate-bug-id-1', 100001, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0);
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 ('bug-relate-case-default-id', 'bug_relate_case', 'default-relate-bug-id', 'FUNCTIONAL', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
VALUES ('bug-relate-case-default-id', 'bug_relate_case', 'default-relate-bug-id', 'FUNCTIONAL', 'test-plan-id', null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('bug-relate-case-default-id-1', 'bug_relate_case', 'default-relate-bug-id-1', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('bug-relate-case-default-id-2', 'bug_relate_case-1', 'default-relate-bug-id', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case-1', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
('bug-relate-case-default-id-2', 'bug_relate_case-1', 'default-relate-bug-id', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case-1', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('bug-relate-case-default-id-3', null, 'default-relate-bug-id', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case-3', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);

View File

@ -9,9 +9,9 @@ INSERT INTO user(id, name, email, password, create_time, update_time, language,
('oasis-user-id4', 'oasis4', 'oasis4@test.com', MD5('metersphere'), UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, NULL, NUll, '', 'LOCAL', NULL, 'admin', 'admin', false);
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag, platform_bug_id, deleted) VALUE
('default-bug-id-for-comment', 100099, 'default-bug-for-comment', 'oasis', 'oasis', 'oasis-user-id', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0),
('default-bug-id-for-comment1', 100099, 'default-bug-for-comment', 'oasis', 'oasis', 'oasis-user-id', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0);
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUE
('default-bug-id-for-comment', 100099, 'default-bug-for-comment', 'oasis', 'oasis', 'oasis-user-id', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0),
('default-bug-id-for-comment1', 100099, 'default-bug-for-comment', 'oasis', 'oasis', 'oasis-user-id', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0);
INSERT INTO bug_comment (id, bug_id, reply_user, notifier, parent_id, content, create_user, create_time, update_user, update_time) VALUES
('default-bug-comment-id-1', 'default-bug-id-for-comment', null, null, null, 'This is a test comment!', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000),

View File

@ -1,7 +1,7 @@
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag, platform_bug_id, deleted) VALUES
('bug_id_1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0),
('bug_id_2', 100001, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0);
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUES
('bug_id_1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0),
('bug_id_2', 100001, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0);
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 ('wx_test_id_1', 'wx_1', 'bug_id_1', 'FUNCTIONAL', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),

View File

@ -0,0 +1,12 @@
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time) VALUE
('bug-trash-project-tmp', null, '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUES
('trash-bug-1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'bug-trash-project', 'bug-template-id', 'Local', 'open', null, null, 1),
('trash-bug-2', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'bug-trash-project', 'default-bug-template-id', 'Local', 'open', null, null, 1),
('trash-bug-3', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'bug-trash-project', 'default-bug-template-id', 'Local', 'open', null, null, 1),
('trash-bug-4', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'bug-trash-project', 'default-bug-template-id', 'Local', 'open', null, null, 1),
('trash-bug-5', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'bug-trash-project', 'default-bug-template-id', 'Jira', 'open', null, null, 1);

View File

@ -1,5 +1,6 @@
package io.metersphere.functional.mapper;
import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.dto.BaseFunctionalCaseBatchDTO;
import io.metersphere.functional.dto.FunctionalCasePageDTO;
@ -7,6 +8,8 @@ import io.metersphere.functional.dto.FunctionalCaseVersionDTO;
import io.metersphere.functional.request.FunctionalCaseBatchMoveRequest;
import io.metersphere.functional.request.FunctionalCasePageRequest;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -58,4 +61,19 @@ public interface ExtFunctionalCaseMapper {
Long getLastPos(@Param("projectId") String projectId, @Param("basePos") Long basePos);
/**
* 获取缺陷未关联的功能用例列表
* @param request provider参数
* @param deleted 是否删除状态
* @return 通用的列表Case集合
*/
List<TestCaseProviderDTO> listUnRelatedCaseWithBug(@Param("request") TestCasePageProviderRequest request, @Param("deleted") boolean deleted);
/**
* 根据关联条件获取关联的用例ID
* @param request 关联参数
* @param deleted 是否删除状态
* @return 关联的用例ID集合
*/
List<String> getSelectIdsByAssociateParam(@Param("request")AssociateOtherCaseRequest request, @Param("deleted") boolean deleted);
}

View File

@ -30,6 +30,10 @@
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
</resultMap>
<resultMap id="TestCaseProviderDTO" type="io.metersphere.dto.TestCaseProviderDTO">
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
</resultMap>
<select id="getPos" resultType="java.lang.Long">
SELECT
pos
@ -633,4 +637,69 @@
</if>
order by `pos` desc limit 1;
</select>
<select id="listUnRelatedCaseWithBug" resultMap="TestCaseProviderDTO">
select
fc.id,
fc.num,
fc.name,
fc.project_id,
fc.tags,
pv.name as versionName
from functional_case fc left join project_version pv ON fc.version_id = pv.id
left join functional_case_module fcm on fcm.id = fc.module_id
where fc.deleted = #{deleted}
and fc.project_id = #{request.projectId}
and fc.version_id = #{request.versionId}
and fc.id not in
(
select brc.case_id from bug_relation_case brc where brc.bug_id = #{request.sourceId} and brc.case_type = #{request.sourceType}
)
<include refid="queryByTestCaseProviderParam"/>
order by fc.create_time desc
</select>
<select id="getSelectIdsByAssociateParam" resultType="java.lang.String">
select fc.id
from functional_case fc
left join functional_case_module fcm on fcm.id = fc.module_id
where fc.deleted = #{deleted}
and fc.project_id = #{request.projectId}
and fc.version_id = #{request.versionId}
and fc.id not in
(
select brc.case_id from bug_relation_case brc where brc.bug_id = #{request.sourceId} and brc.case_type = #{request.sourceType}
)
<include refid="queryByAssociateParam"/>
</select>
<sql id="queryByTestCaseProviderParam">
<!-- 待补充关联Case弹窗中的高级搜索条件 -->
<if test="request.keyword != null and request.keyword != ''">
and (
fc.id like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%')
)
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and fcm.module_id in
<foreach collection="moduleIds" item="moduleId" open="(" separator="," close=")">
#{moduleId}
</foreach>
</if>
</sql>
<sql id="queryByAssociateParam">
<!-- 待补充关联Case弹窗中的高级搜索条件 -->
<if test="request.condition.keyword != null and request.condition.keyword != ''">
and (
fc.id like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%')
)
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and fcm.module_id in
<foreach collection="moduleIds" item="moduleId" open="(" separator="," close=")">
#{moduleId}
</foreach>
</if>
</sql>
</mapper>

View File

@ -0,0 +1,37 @@
package io.metersphere.functional.provider;
import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.functional.mapper.ExtFunctionalCaseMapper;
import io.metersphere.provider.BaseAssociateCaseProvider;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AssociateCaseProvider implements BaseAssociateCaseProvider {
@Resource
private ExtFunctionalCaseMapper extFunctionalCaseMapper;
@Override
public List<TestCaseProviderDTO> listUnRelatedTestCaseList(TestCasePageProviderRequest testCasePageProviderRequest) {
return extFunctionalCaseMapper.listUnRelatedCaseWithBug(testCasePageProviderRequest, false);
}
@Override
public List<String> getRelatedIdsByParam(AssociateOtherCaseRequest request, boolean deleted) {
if (request.isSelectAll()) {
List<String> relatedIds = extFunctionalCaseMapper.getSelectIdsByAssociateParam(request, deleted);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
relatedIds = relatedIds.stream().filter(id -> !request.getExcludeIds().contains(id)).toList();
}
return relatedIds;
} else {
return request.getSelectIds();
}
}
}

View File

@ -0,0 +1,49 @@
package io.metersphere.functional.controller;
import io.metersphere.functional.provider.AssociateCaseProvider;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.system.base.BaseTest;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import java.util.List;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class AssociateCaseProviderTests extends BaseTest {
@Resource
AssociateCaseProvider associateCaseProvider;
@Test
@Order(1)
@Sql(scripts = {"/dml/init_associate_case_provider_test.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
void coverCaseProvider() {
TestCasePageProviderRequest request = new TestCasePageProviderRequest();
request.setProjectId("test-pro");
request.setVersionId("test-ver");
request.setSourceId("test-source-id");
request.setSourceType("test-source-type");
associateCaseProvider.listUnRelatedTestCaseList(request);
AssociateOtherCaseRequest associateRequest = new AssociateOtherCaseRequest();
associateRequest.setSelectAll(true);
associateRequest.setProjectId("select-case-pro");
associateRequest.setVersionId("v1.0.0");
associateRequest.setSourceId("test-source-id");
associateRequest.setSourceType("test-source-type");
associateCaseProvider.getRelatedIdsByParam(associateRequest, false);
associateRequest.setExcludeIds(List.of("select-case"));
associateCaseProvider.getRelatedIdsByParam(associateRequest, false);
associateRequest.setSelectAll(false);
associateRequest.setSelectIds(List.of("select-case"));
}
}

View File

@ -0,0 +1,8 @@
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
VALUES ('select-case-tmp', 100, 'select-case-module', 'select-case-pro', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
VALUES ('select-case', 100, 'select-case-module', 'select-case-pro', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
INSERT INTO functional_case_module(id, project_id, name, parent_id, pos, create_time, create_user, update_time, update_user)
VALUES ('select-case-module', 'select-case-pro', 'test_module_name', 'NONE', '1', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin');

View File

@ -1,7 +1,7 @@
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag, platform_bug_id, deleted) VALUES
('wx_bug_id_1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0),
('wx_bug_id_2', 100001, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0);
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUES
('wx_bug_id_1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0),
('wx_bug_id_2', 100001, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0);
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', 'wx_1', 'wx_bug_id_1', 'FUNCTIONAL', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),

View File

@ -1,34 +1,34 @@
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags,
platform_bug_id, deleted)
VALUES ('sty-file-association-bug-id-1', 100000, 'sty-default-bug-1', 'oasis', 'oasis', 'admin',
UNIX_TIMESTAMP() * 1000,
'admin',
UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id',
'Local', 'open', 'default-tag', null, 0);
'Local', 'open', '["default-tag"]', null, 0);
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags,
platform_bug_id, deleted)
VALUES ('sty-file-association-bug-id-2', 100001, 'sty-default-bug-2', 'oasis', 'oasis', 'admin',
UNIX_TIMESTAMP() * 1000,
'admin',
UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id',
'Local', 'open', 'default-tag', null, 0);
'Local', 'open', '["default-tag"]', null, 0);
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags,
platform_bug_id, deleted)
VALUES ('sty-file-association-bug-id-3', 100002, 'sty-default-bug-3', 'oasis', 'oasis', 'admin',
UNIX_TIMESTAMP() * 1000,
'admin',
UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id',
'Local', 'open', 'default-tag', null, 0);
'Local', 'open', '["default-tag"]', null, 0);
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags,
platform_bug_id, deleted)
VALUES ('sty-file-association-bug-id-4', 100003, 'sty-default-bug-4', 'oasis', 'oasis', 'admin',
UNIX_TIMESTAMP() * 1000,
'admin',
UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id',
'Local', 'open', 'default-tag', null, 0);
'Local', 'open', '["default-tag"]', null, 0);

View File

@ -21,7 +21,7 @@ INSERT INTO project_application (project_id, type, type_value) VALUES
('default-project-for-application', 'BUG_SYNC_MECHANISM', 'increment'),
('default-project-for-application', 'BUG_SYNC_PLATFORM_KEY', 'jira'),
('default-project-for-application', 'BUG_SYNC_SYNC_ENABLE', 'true'),
('default-project-for-application', 'CASE_ENABLE', 'false'),
('default-project-for-application', 'CASE_RELATED_CASE_ENABLE', 'false'),
('default-project-for-application', 'CASE_RELATED_DEMAND_PLATFORM_CONFIG', '{"jiraKey":"TES","jiraDemandTypeId":"10007"}'),
('default-project-for-application', 'CASE_RELATED_PLATFORM_KEY', 'jira');

View File

@ -0,0 +1,57 @@
package io.metersphere.system.dto.request;
import io.metersphere.sdk.constants.CustomFieldType;
import io.metersphere.system.domain.CustomFieldOption;
import io.metersphere.system.uid.IDGenerator;
import java.util.Arrays;
import java.util.List;
/**
* 默认的功能性自定义字段
* 方便初始化项目模板
*/
public enum DefaultBugCustomField {
/**
* 严重程度
*/
DEGREE("bug_degree", CustomFieldType.SELECT,
Arrays.asList(
getNewOption(IDGenerator.nextStr(), "提示"),
getNewOption(IDGenerator.nextStr(), "一般"),
getNewOption(IDGenerator.nextStr(), "严重"),
getNewOption(IDGenerator.nextStr(), "致命")
)
);
private final String name;
private final CustomFieldType type;
private final List<CustomFieldOption> options;
DefaultBugCustomField(String name, CustomFieldType type, List<CustomFieldOption> options) {
this.name = name;
this.type = type;
this.options = options;
}
public CustomFieldType getType() {
return type;
}
public String getName() {
return name;
}
public List<CustomFieldOption> getOptions() {
return options;
}
private static CustomFieldOption getNewOption(String value, String text) {
CustomFieldOption customFieldOption = new CustomFieldOption();
customFieldOption.setValue(value);
customFieldOption.setText(text);
customFieldOption.setInternal(true);
return customFieldOption;
}
}

View File

@ -10,6 +10,7 @@ import io.metersphere.system.domain.CustomField;
import io.metersphere.system.domain.CustomFieldExample;
import io.metersphere.system.domain.CustomFieldOption;
import io.metersphere.system.domain.TemplateCustomFieldExample;
import io.metersphere.system.dto.request.DefaultBugCustomField;
import io.metersphere.system.dto.request.DefaultFunctionalCustomField;
import io.metersphere.system.dto.sdk.CustomFieldDTO;
import io.metersphere.system.dto.sdk.request.CustomFieldOptionRequest;
@ -290,4 +291,27 @@ public class BaseCustomFieldService {
}
return customFields;
}
/**
* 初始化缺陷模板默认字段
* @param scopeType 模板范围
* @param scopeId 范围ID
* @return
*/
public List<CustomField> initBugDefaultCustomField(TemplateScopeType scopeType, String scopeId) {
List<CustomField> customFields = new ArrayList<>();
for (DefaultBugCustomField defaultBugCustomField : DefaultBugCustomField.values()) {
CustomField customField = new CustomField();
customField.setName(defaultBugCustomField.getName());
customField.setScene(TemplateScene.BUG.name());
customField.setType(defaultBugCustomField.getType().name());
customField.setScopeType(scopeType.name());
customField.setScopeId(scopeId);
customField.setEnableOptionKey(false);
customFields.add(this.initDefaultCustomField(customField));
// 初始化选项
baseCustomFieldOptionService.addByFieldId(customField.getId(), defaultBugCustomField.getOptions());
}
return customFields;
}
}

View File

@ -260,8 +260,22 @@ public class BaseStatusFlowSettingService {
return statusItems;
}
/**
* 获取所有状态选项
* @return 状态选项集合
*/
public List<SelectOption> getAllStatusOption(String scopeId, String scene) {
// 获取所有状态选项值
List<StatusItem> statusItems = baseStatusItemService.getByScopeIdAndScene(scopeId, scene);
statusItems = baseStatusItemService.translateInternalStatusItem(statusItems);
return statusItems.stream().map(item -> new SelectOption(item.getName(), item.getId())).toList();
}
/**
* 获取状态流转选项
* @param scopeId 项目或组织ID
* @param scene 场景
* @param targetStatusId 目标状态ID
* @return 状态选项集合
*/
public List<SelectOption> getStatusTransitions(String scopeId, String scene, String targetStatusId) {

View File

@ -24,6 +24,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -114,6 +115,7 @@ public class BaseTemplateService {
// 封装自定义字段信息
List<TemplateCustomFieldDTO> fieldDTOS = templateCustomFields.stream()
.filter(i -> !BooleanUtils.isTrue(i.getSystemField()))
.sorted(Comparator.comparingInt(TemplateCustomField::getPos))
.map(i -> {
CustomField customField = fieldMap.get(i.getFieldId());
TemplateCustomFieldDTO templateCustomFieldDTO = new TemplateCustomFieldDTO();
@ -348,7 +350,18 @@ public class BaseTemplateService {
* @param scopeType
*/
public void initBugDefaultTemplate(String scopeId, TemplateScopeType scopeType) {
this.initDefaultTemplate(scopeId, "bug_default", scopeType, TemplateScene.BUG);
// 初始化字段
List<CustomField> customFields = baseCustomFieldService.initBugDefaultCustomField(scopeType, scopeId);
// 初始化模板
Template template = this.initDefaultTemplate(scopeId, "bug_default", scopeType, TemplateScene.BUG);
// 初始化模板和字段的关联关系
List<TemplateCustomFieldRequest> templateCustomFieldRequests = customFields.stream().map(customField -> {
TemplateCustomFieldRequest templateCustomFieldRequest = new TemplateCustomFieldRequest();
templateCustomFieldRequest.setRequired(true);
templateCustomFieldRequest.setFieldId(customField.getId());
return templateCustomFieldRequest;
}).toList();
baseTemplateCustomFieldService.addCustomFieldByTemplateId(template.getId(), templateCustomFieldRequests);
}
public void initApiDefaultTemplate(String scopeId, TemplateScopeType scopeType) {

View File

@ -26,5 +26,6 @@ public class BaseStatusFlowSettingTests {
baseStatusFlowSettingService.getStatusTransitions("default-project-for-status", "BUG", null);
baseStatusFlowSettingService.getStatusTransitions("default-project-for-status", "BUG", "1");
baseStatusFlowSettingService.getStatusTransitions("default-project-for-status", "BUG", "2");
baseStatusFlowSettingService.getAllStatusOption("default-project-for-status", "BUG");
}
}

View File

@ -15,10 +15,7 @@ import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.domain.*;
import io.metersphere.system.dto.*;
import io.metersphere.system.dto.request.DefaultFunctionalCustomField;
import io.metersphere.system.dto.request.ProjectAddMemberRequest;
import io.metersphere.system.dto.request.ProjectMemberRequest;
import io.metersphere.system.dto.request.ProjectRequest;
import io.metersphere.system.dto.request.*;
import io.metersphere.system.dto.sdk.request.CustomFieldOptionRequest;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.dto.sdk.request.TemplateCustomFieldRequest;
@ -524,7 +521,7 @@ public class SystemProjectControllerTests extends BaseTest {
Project project = projectMapper.selectByPrimaryKey(projectId);
// 校验是否初始化了项目字段
List<CustomField> fields = baseCustomFieldService.getByScopeId(project.getId());
Assertions.assertEquals(fields.size(), DefaultFunctionalCustomField.values().length);
Assertions.assertEquals(fields.size(), DefaultFunctionalCustomField.values().length + DefaultBugCustomField.values().length);
for (DefaultFunctionalCustomField value : DefaultFunctionalCustomField.values()) {
CustomField customField = fields.stream()
.filter(field -> StringUtils.equals(field.getName(), value.getName()))