diff --git a/backend/framework/domain/src/main/java/io/metersphere/bug/domain/Bug.java b/backend/framework/domain/src/main/java/io/metersphere/bug/domain/Bug.java index 591b17fcc2..c453f193b9 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/bug/domain/Bug.java +++ b/backend/framework/domain/src/main/java/io/metersphere/bug/domain/Bug.java @@ -68,7 +68,7 @@ public class Bug implements Serializable { private String status; @Schema(description = "标签") - private String tag; + private java.util.List 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), diff --git a/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugExample.java b/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugExample.java index 602703bd4c..a295a86ee8 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugExample.java +++ b/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugExample.java @@ -64,19 +64,50 @@ public class BugExample { } protected abstract static class GeneratedCriteria { + protected List tagsCriteria; + + protected List allCriteria; + protected List criteria; protected GeneratedCriteria() { super(); criteria = new ArrayList(); + tagsCriteria = new ArrayList(); + } + + public List 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 value1, List 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 getAllCriteria() { - return criteria; + if (allCriteria == null) { + allCriteria = new ArrayList(); + allCriteria.addAll(criteria); + allCriteria.addAll(tagsCriteria); + } + return allCriteria; } public List 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 value) { + addTagsCriterion("tags =", value, "tags"); return (Criteria) this; } - public Criteria andTagNotEqualTo(String value) { - addCriterion("tag <>", value, "tag"); + public Criteria andTagsNotEqualTo(List value) { + addTagsCriterion("tags <>", value, "tags"); return (Criteria) this; } - public Criteria andTagGreaterThan(String value) { - addCriterion("tag >", value, "tag"); + public Criteria andTagsGreaterThan(List value) { + addTagsCriterion("tags >", value, "tags"); return (Criteria) this; } - public Criteria andTagGreaterThanOrEqualTo(String value) { - addCriterion("tag >=", value, "tag"); + public Criteria andTagsGreaterThanOrEqualTo(List value) { + addTagsCriterion("tags >=", value, "tags"); return (Criteria) this; } - public Criteria andTagLessThan(String value) { - addCriterion("tag <", value, "tag"); + public Criteria andTagsLessThan(List value) { + addTagsCriterion("tags <", value, "tags"); return (Criteria) this; } - public Criteria andTagLessThanOrEqualTo(String value) { - addCriterion("tag <=", value, "tag"); + public Criteria andTagsLessThanOrEqualTo(List value) { + addTagsCriterion("tags <=", value, "tags"); return (Criteria) this; } - public Criteria andTagLike(String value) { - addCriterion("tag like", value, "tag"); + public Criteria andTagsLike(List value) { + addTagsCriterion("tags like", value, "tags"); return (Criteria) this; } - public Criteria andTagNotLike(String value) { - addCriterion("tag not like", value, "tag"); + public Criteria andTagsNotLike(List value) { + addTagsCriterion("tags not like", value, "tags"); return (Criteria) this; } - public Criteria andTagIn(List values) { - addCriterion("tag in", values, "tag"); + public Criteria andTagsIn(List> values) { + addTagsCriterion("tags in", values, "tags"); return (Criteria) this; } - public Criteria andTagNotIn(List values) { - addCriterion("tag not in", values, "tag"); + public Criteria andTagsNotIn(List> 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 value1, List 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 value1, List value2) { + addTagsCriterion("tags not between", value1, value2, "tags"); return (Criteria) this; } diff --git a/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugLocalAttachment.java b/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugLocalAttachment.java index c6ba450a48..aeccbaef8d 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugLocalAttachment.java +++ b/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugLocalAttachment.java @@ -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); diff --git a/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugLocalAttachmentExample.java b/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugLocalAttachmentExample.java index e8ee194d85..44a7b29880 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugLocalAttachmentExample.java +++ b/backend/framework/domain/src/main/java/io/metersphere/bug/domain/BugLocalAttachmentExample.java @@ -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 values) { + addCriterion("`source` in", values, "source"); + return (Criteria) this; + } + + public Criteria andSourceNotIn(List 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; diff --git a/backend/framework/domain/src/main/java/io/metersphere/bug/mapper/BugLocalAttachmentMapper.xml b/backend/framework/domain/src/main/java/io/metersphere/bug/mapper/BugLocalAttachmentMapper.xml index 064f08a545..46d9fbe81f 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/bug/mapper/BugLocalAttachmentMapper.xml +++ b/backend/framework/domain/src/main/java/io/metersphere/bug/mapper/BugLocalAttachmentMapper.xml @@ -7,6 +7,7 @@ + @@ -69,7 +70,7 @@ - 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 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 into bug @@ -172,8 +210,8 @@ `status`, - - tag, + + tags, platform_bug_id, @@ -228,8 +266,8 @@ #{status,jdbcType=VARCHAR}, - - #{tag,jdbcType=VARCHAR}, + + #{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler}, #{platformBugId,jdbcType=VARCHAR}, @@ -293,8 +331,8 @@ `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}, @@ -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 @@ `status` = #{status,jdbcType=VARCHAR}, - - tag = #{tag,jdbcType=VARCHAR}, + + tags = #{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler}, 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 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 @@ -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}) @@ -480,8 +518,8 @@ #{item.status,jdbcType=VARCHAR} - - #{item.tag,jdbcType=VARCHAR} + + #{item.tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler} #{item.platformBugId,jdbcType=VARCHAR} diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_7__bug_management.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_7__bug_management.sql index 70deb3f4fe..9dad4e9ee6 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_7__bug_management.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_7__bug_management.sql @@ -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' , diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql index b3c04814b7..bdf68697a7 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql @@ -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'); diff --git a/backend/framework/provider/src/main/java/io/metersphere/dto/BugProviderDTO.java b/backend/framework/provider/src/main/java/io/metersphere/dto/BugProviderDTO.java index 7730997c3b..0a90a33e9e 100644 --- a/backend/framework/provider/src/main/java/io/metersphere/dto/BugProviderDTO.java +++ b/backend/framework/provider/src/main/java/io/metersphere/dto/BugProviderDTO.java @@ -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 tags; @Schema(description = "创建时间") private Long createTime; diff --git a/backend/framework/provider/src/main/java/io/metersphere/provider/BaseAssociateCaseProvider.java b/backend/framework/provider/src/main/java/io/metersphere/provider/BaseAssociateCaseProvider.java new file mode 100644 index 0000000000..e53734c37d --- /dev/null +++ b/backend/framework/provider/src/main/java/io/metersphere/provider/BaseAssociateCaseProvider.java @@ -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 listUnRelatedTestCaseList(TestCasePageProviderRequest testCasePageProviderRequest); + + /** + * 根据关联条件获取关联的用例ID + * @param request 请求参数 + * @param deleted 是否删除状态 + * @return 用例ID集合 + */ + List getRelatedIdsByParam(AssociateOtherCaseRequest request, boolean deleted); +} diff --git a/backend/framework/sdk/src/main/resources/i18n/bug.properties b/backend/framework/sdk/src/main/resources/i18n/bug.properties index c1ae3d1df4..c525c8dd93 100644 --- a/backend/framework/sdk/src/main/resources/i18n/bug.properties +++ b/backend/framework/sdk/src/main/resources/i18n/bug.properties @@ -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之间 diff --git a/backend/framework/sdk/src/main/resources/i18n/bug_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/bug_en_US.properties index a950440f4c..3d7f221196 100644 --- a/backend/framework/sdk/src/main/resources/i18n/bug_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/bug_en_US.properties @@ -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 diff --git a/backend/framework/sdk/src/main/resources/i18n/bug_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/bug_zh_CN.properties index bb9d8a054e..32ef0cda50 100644 --- a/backend/framework/sdk/src/main/resources/i18n/bug_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/bug_zh_CN.properties @@ -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之间 diff --git a/backend/framework/sdk/src/main/resources/i18n/bug_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/bug_zh_TW.properties index 38f48f3c50..0ab17df9ee 100644 --- a/backend/framework/sdk/src/main/resources/i18n/bug_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/bug_zh_TW.properties @@ -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之間 diff --git a/backend/framework/sdk/src/main/resources/i18n/commons.properties b/backend/framework/sdk/src/main/resources/i18n/commons.properties index e862cb86b5..4965dcacfc 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons.properties @@ -456,6 +456,7 @@ template_scene_illegal_error=使用场景不合法 # 内置的模板或字段 custom_field.functional_priority=用例等级 +custom_field.bug_degree=严重程度 template.default=默认模板 parent.node.not_blank=父节点不能为空 diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties index 8a26dec9b2..48b961ab5d 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties @@ -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 diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties index 56a8186b95..dea17f525f 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties @@ -458,6 +458,7 @@ scheduled_tasks=定时任务 template_scene_illegal_error=使用场景不合法 # 内置的模板或字段 custom_field.functional_priority=用例等级 +custom_field.bug_degree=严重程度 template.default=默认模板 set_default_template=设置默认模板 diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties index f808947294..e24c898f24 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties @@ -457,6 +457,7 @@ template_scene_illegal_error=使用場景不合法 # 内置的模板或字段 custom_field.functional_priority=用例等級 +custom_field.bug_degree=嚴重程度 template.default=默認模板 set_default_template=設置默認模板 diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugController.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugController.java index cbe01761d9..21670166bd 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugController.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugController.java @@ -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 getHeaderFields(@PathVariable String projectId) { + return bugService.getHeaderCustomFields(projectId); + } + + @GetMapping("/header/status-option/{projectId}") + @Operation(summary = "缺陷管理-获取表头状态选项") + @RequiresPermissions(PermissionConstants.PROJECT_BUG_READ) + public List getHeaderStatusOption(@PathVariable String projectId) { + return bugStatusService.getHeaderStatusOption(projectId); + } + + @GetMapping("/header/handler-option/{projectId}") + @Operation(summary = "缺陷管理-获取表头处理人选项") + @RequiresPermissions(PermissionConstants.PROJECT_BUG_READ) + public List 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 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 export(@Validated @RequestBody BugExportRequest request) throws Exception { - return bugService.export(request, SessionUtils.getUserId()); + return bugService.export(request); } } diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugRelateCaseController.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugRelateCaseController.java index 48b3b2023c..923dcf3e14 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugRelateCaseController.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugRelateCaseController.java @@ -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> unRelatedPage(@Validated @RequestBody TestCasePageProviderRequest request) { + // 目前只保留功能用例的Provider接口, 后续其他用例根据RelateCaseType扩展 + Page 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 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 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> page(@Validated @RequestBody BugRelatedCasePageRequest request) { Page 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); } } diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugTrashController.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugTrashController.java new file mode 100644 index 0000000000..7552671721 --- /dev/null +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/controller/BugTrashController.java @@ -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> page(@Validated @RequestBody BugPageRequest request) { + Page 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); + } +} diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/BugSyncResult.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/BugSyncResult.java new file mode 100644 index 0000000000..524e85d568 --- /dev/null +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/BugSyncResult.java @@ -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; +} diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/BugTemplateInjectField.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/BugTemplateInjectField.java index 070fc23bda..51a5aed281 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/BugTemplateInjectField.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/BugTemplateInjectField.java @@ -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; diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugBatchRequest.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugBatchRequest.java index 614e0af12f..ae1ccf42a8 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugBatchRequest.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugBatchRequest.java @@ -11,4 +11,7 @@ public class BugBatchRequest extends TableBatchProcessDTO { @Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED) private String projectId; + + @Schema(description = "是否回收站") + private boolean useTrash; } diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugBatchUpdateRequest.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugBatchUpdateRequest.java index 2eede37abc..4bdf6adcd8 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugBatchUpdateRequest.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugBatchUpdateRequest.java @@ -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 tags; @Schema(description = "是否追加", requiredMode = Schema.RequiredMode.REQUIRED) private boolean append; + + @Schema(description = "更新人") + private String updateUser; + + @Schema(description = "更新时间") + private Long updateTime; } diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugEditRequest.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugEditRequest.java index 2461fcd65f..5f38dd04e3 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugEditRequest.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugEditRequest.java @@ -38,7 +38,7 @@ public class BugEditRequest { private String templateId; @Schema(description = "标签") - private String tag; + private List tags; @Schema(description = "缺陷内容") private String description; diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugRelateCaseModuleRequest.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugRelateCaseModuleRequest.java new file mode 100644 index 0000000000..6ddff011cb --- /dev/null +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugRelateCaseModuleRequest.java @@ -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; +} diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/response/BugDTO.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/response/BugDTO.java index 43d8c08d5b..60e8f69ce1 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/response/BugDTO.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/response/BugDTO.java @@ -27,9 +27,6 @@ public class BugDTO extends Bug { @Schema(description = "处理人名称") private String handleUserName; - @Schema(description = "状态名称") - private String statusName; - @Schema(description = "关联用例数量") private Integer relationCaseCount; diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/response/BugTagEditDTO.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/response/BugTagEditDTO.java index adabb02f74..77e177da6e 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/response/BugTagEditDTO.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/response/BugTagEditDTO.java @@ -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 tags; } diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/enums/BugAttachmentSourceType.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/enums/BugAttachmentSourceType.java new file mode 100644 index 0000000000..dbdf8b32bf --- /dev/null +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/enums/BugAttachmentSourceType.java @@ -0,0 +1,13 @@ +package io.metersphere.bug.enums; + +public enum BugAttachmentSourceType { + + /** + * 附件 + */ + ATTACHMENT, + /** + * MarkDown编辑器 + */ + MD; +} diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugLocalAttachmentMapper.xml b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugLocalAttachmentMapper.xml index 4923180242..be8a0a4c50 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugLocalAttachmentMapper.xml +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugLocalAttachmentMapper.xml @@ -5,7 +5,7 @@ insert into bug_local_attachment values (#{attachment.id}, #{attachment.bugId}, #{attachment.fileId}, #{attachment.fileName}, - #{attachment.size}, #{attachment.createUser}, #{attachment.createTime}) + #{attachment.size}, #{attachment.source}, #{attachment.createUser}, #{attachment.createTime}) \ No newline at end of file diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugMapper.xml b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugMapper.xml index ae30d94a04..538345c469 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugMapper.xml +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugMapper.xml @@ -1,9 +1,21 @@ - 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 @@ -12,7 +24,7 @@ - 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} + - + select id as bugId, tags from bug where id in #{id} @@ -48,8 +62,14 @@ update bug - - tag = #{request.tag}, + + tags = #{request.tags, typeHandler=io.metersphere.handler.ListTypeHandler}, + + + update_user = #{updateUser}, + + + update_time = #{updateTime}, where id in @@ -58,6 +78,34 @@ + + + + @@ -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},'%') ) + + @@ -87,22 +138,27 @@ + and b.handle_user in + and b.create_user in + and b.platform in + and b.status in + and b.id in ( select bug_id from bug_custom_field where concat('custom_single_', field_id) = #{key} @@ -110,7 +166,8 @@ ) - + + and b.id in ( select bug_id from bug_custom_field where concat('custom_multiple_', field_id) = #{key} and @@ -125,98 +182,181 @@ - - and b.handle_user - - + and ( + + + - - - and b.create_user - - - - - - and b.platform - - - - - - and b.status - - - - - - - - - and b.id not in ( + + + + + + b.num + + + + + + + + + + b.title + + + + + + + + + + b.platform + + + + + + + + + + b.handle_user + + + + + + + + + + b.status + + + + + + + + + + b.create_user + + + + + + + + + + b.create_time + + + + + + + + + + + + + + + + + + + + b.id not in ( + + + b.id in ( + + select bug_id from bug_custom_field where field_id = #{custom.id} and + + + + JSON_CONTAINS(`value`, JSON_ARRAY(#{val})) + + + + left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13) + + + + + + trim(both '"' from `value`) + + + + + + ) - - and b.id in ( - - select bug_id from bug_custom_field where field_id = #{custom.id} - - - and `value` - - - - - - and ${custom.value} - - - and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13) - - - - - - and trim(both '"' from `value`) - - - - - - ) - - - + + + ) - - - - + + + + + + + + JSON_CONTAINS(b.tags, JSON_ARRAY(#{tag})) + + + + + + + + (b.tags is null or b.tags = '[]') + + diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugRelateCaseMapper.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugRelateCaseMapper.java index 346b00bb74..1307867122 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugRelateCaseMapper.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugRelateCaseMapper.java @@ -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 getRelateCaseModule(@Param("request") BugRelateCaseModuleRequest request, @Param("deleted") boolean deleted); + + /** + * 获取缺陷关联的用例模块树数量 + * @param request 请求参数 + * @param deleted 是否删除状态 + * @return 模块树数量 + */ + List countRelateCaseModuleTree(@Param("request") BugRelateCaseModuleRequest request, @Param("deleted") boolean deleted); + /** * 统计缺陷关联的用例数量 * diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugRelateCaseMapper.xml b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugRelateCaseMapper.xml index 0c31406bfd..aff83ea773 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugRelateCaseMapper.xml +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugRelateCaseMapper.xml @@ -1,6 +1,41 @@ + + + + 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},'%') + + + + + and ( + fc.id like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%') + ) + + + and fcm.module_id in + + #{moduleId} + + + \ No newline at end of file diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/provider/AssociateBugProvider.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/provider/AssociateBugProvider.java index dfc22cfe82..840c46b11b 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/provider/AssociateBugProvider.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/provider/AssociateBugProvider.java @@ -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 diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugRelateCaseService.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugRelateCaseCommonService.java similarity index 53% rename from backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugRelateCaseService.java rename to backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugRelateCaseCommonService.java index 6b5866e1a6..08897e9f2f 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugRelateCaseService.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugRelateCaseCommonService.java @@ -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 getRelateCaseTree(BugRelateCaseModuleRequest request) { + // 目前只保留功能用例的左侧模块树方法调用, 后续其他用例根据RelateCaseType扩展 + List relateCaseModules = extBugRelateCaseMapper.getRelateCaseModule(request, false); + // 构建模块树层级数量为通用逻辑 + return super.buildTreeAndCountResource(relateCaseModules, true, Translator.get("api_unplanned_request")); + } + + /** + * 获取关联用例模块树数量 + * @param request 请求参数 + * @return 模块树集合 + */ + public Map countTree(BugRelateCaseModuleRequest request) { + // 目前只保留功能用例的左侧模块树方法调用, 后续其他用例根据RelateCaseType扩展 + List moduleCounts = extBugRelateCaseMapper.countRelateCaseModuleTree(request, false); + List relateCaseModules = extBugRelateCaseMapper.getRelateCaseModule(request, false); + List relateCaseModuleWithCount = buildTreeAndCountResource(relateCaseModules, moduleCounts, true, Translator.get("api_unplanned_request")); + Map 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 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 planRelatedCases = bugRelationCaseMapper.selectByExample(bugRelationCaseExample); + Map 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 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) { + + } } diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java index 39b3e99e77..0a095e315b 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java @@ -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 list(BugPageRequest request, String currentUser) { - CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, currentUser); + public List list(BugPageRequest request) { List bugs = extBugMapper.list(request); if (CollectionUtils.isEmpty(bugs)) { return new ArrayList<>(); } - // 处理自定义字段及状态字段 - List bugList = handleCustomFieldsAndStatus(bugs, request.getProjectId()); + // 处理自定义字段 + List 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 batchIds = getBatchIdsByRequest(request, currentUser); + public void batchDelete(BugBatchRequest request) { + List batchIds = getBatchIdsByRequest(request); batchIds.forEach(this::delete); } + /** + * 批量恢复缺陷 + * @param request 请求参数 + */ + public void batchRecover(BugBatchRequest request) { + List batchIds = getBatchIdsByRequest(request); + batchIds.forEach(this::recover); + } + + /** + * 批量彻底删除缺陷 + * @param request 请求参数 + */ + public void batchDeleteTrash(BugBatchRequest request) { + List batchIds = getBatchIdsByRequest(request); + batchIds.forEach(this::deleteTrash); + } + /** * 批量编辑缺陷 * @param request 请求参数 + * @param currentUser 当前用户 */ public void batchUpdate(BugBatchUpdateRequest request, String currentUser) { - List batchIds = getBatchIdsByRequest(request, currentUser); + List batchIds = getBatchIdsByRequest(request); // 目前只做标签的批量编辑 if (request.isAppend()) { // 标签(追加) List bugTagList = extBugMapper.getBugTagList(batchIds); - Map bugTagMap = bugTagList.stream().collect(Collectors.toMap(BugTagEditDTO::getBugId, b -> Optional.ofNullable(b.getTag()).orElse(StringUtils.EMPTY))); + Map> 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 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 injectFieldList = JSON.parseArray(injectFields, BugTemplateInjectField.class); + List 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 projectMembers = projectMemberService.listMember(request); - List 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 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 relationCaseCount = extBugRelateCaseMapper.countRelationCases(ids); Map 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 handleCustomFieldsAndStatus(List bugs, String projectId) { + private List handleCustomField(List bugs, String projectId) { List ids = bugs.stream().map(BugDTO::getId).toList(); List customFields = extBugCustomFieldMapper.getBugAllCustomFields(ids, projectId); Map> customFieldMap = customFields.stream().collect(Collectors.groupingBy(BugCustomFieldDTO::getBugId)); @@ -940,6 +973,7 @@ public class BugService { * @return 平台字段集合 */ public List transferCustomToPlatformField(String templateId, List customFields, boolean noApiFilter) { + List platformCustomFields = new ArrayList<>(customFields); if (!noApiFilter) { // 过滤出API映射的字段 List 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 export(BugExportRequest request, String currentUser) throws Exception { - List bugs = this.getExportDataByBatchRequest(request, currentUser); + public ResponseEntity export(BugExportRequest request) throws Exception { + List 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 getExportDataByBatchRequest(BugBatchRequest request, String currentUser) { + private List getExportDataByBatchRequest(BugBatchRequest request) { if (request.isSelectAll()) { // 全选{根据查询条件查询所有数据, 排除取消勾选的数据} BugPageRequest bugPageRequest = new BugPageRequest(); BeanUtils.copyBean(bugPageRequest, request); bugPageRequest.setUseTrash(false); - CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(bugPageRequest, currentUser); List 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 getBatchIdsByRequest(BugBatchRequest request, String currentUser) { + private List getBatchIdsByRequest(BugBatchRequest request) { if (request.isSelectAll()) { // 全选{根据查询条件查询所有数据, 排除取消勾选的数据} BugPageRequest bugPageRequest = new BugPageRequest(); BeanUtils.copyBean(bugPageRequest, request); - bugPageRequest.setUseTrash(false); - CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(bugPageRequest, currentUser); List 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 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 localHandlerOption = getLocalHandlerOption(projectId); + // 获取插件中自定义的注入字段(处理人) + Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(), + new String(serviceIntegration.getConfiguration())); + List platformHandlerOption = new ArrayList<>(); + List 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 getLocalHandlerOption(String projectId) { + ProjectMemberRequest request = new ProjectMemberRequest(); + request.setProjectId(projectId); + List 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 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 getHeaderCustomFields(String projectId) { + List headerCustomFields = new ArrayList<>(); + // 本地模板 + List