From 0e4cc6bb42616ed35b7fb7b10944ea45bf51b457 Mon Sep 17 00:00:00 2001 From: metersphere-bot <78466014+metersphere-bot@users.noreply.github.com> Date: Wed, 22 Dec 2021 10:22:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BC=BA=E9=99=B7=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AF=84=E8=AE=BA=E5=B9=B6=E4=B8=94=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=AB=99=E5=86=85=E4=BF=A1=E9=80=9A=E7=9F=A5=20(#8703?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 缺陷详情增加评论并且增加站内信通知 --story=1004214 --user=张大海 11.缺陷详情添加评论 以免修改状态无法增加描述 https://www.tapd.cn/55049933/s/1084637 * refactor: 代码优化 Co-authored-by: zhangdahai111 Co-authored-by: jianxing <41557596+AgAngle@users.noreply.github.com> --- .../metersphere/base/domain/IssueComment.java | 23 + .../base/domain/IssueCommentExample.java | 600 ++++++++++++++++++ .../base/mapper/IssueCommentMapper.java | 36 ++ .../base/mapper/IssueCommentMapper.xml | 287 +++++++++ .../mapper/ext/ExtIssueCommentMapper.java | 18 + .../base/mapper/ext/ExtIssueCommentMapper.xml | 15 + .../controller/IssueCommentController.java | 56 ++ .../track/controller/IssuesController.java | 1 + .../track/dto/IssueCommentDTO.java | 9 + .../issues/IssuesRelevanceRequest.java | 4 + .../issues/SaveIssueCommentRequest.java | 11 + .../track/service/IssueCommentService.java | 80 +++ .../track/service/IssuesService.java | 12 +- .../db/migration/V103__v1.16_release.sql | 16 +- .../mail/track/IssuesCommentUpdate.html | 12 + .../track/DefectTaskNotification.vue | 9 + .../track/case/components/TestCaseEdit.vue | 1 + .../components/track/issue/IssueComment.vue | 118 ++++ .../track/issue/IssueEditDetail.vue | 84 ++- .../components/track/issue/IssueList.vue | 1 + .../track/review/commom/ReviewComment.vue | 2 +- .../track/review/commom/ReviewCommentItem.vue | 5 +- 22 files changed, 1389 insertions(+), 11 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/base/domain/IssueComment.java create mode 100644 backend/src/main/java/io/metersphere/base/domain/IssueCommentExample.java create mode 100644 backend/src/main/java/io/metersphere/base/mapper/IssueCommentMapper.java create mode 100644 backend/src/main/java/io/metersphere/base/mapper/IssueCommentMapper.xml create mode 100644 backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssueCommentMapper.java create mode 100644 backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssueCommentMapper.xml create mode 100644 backend/src/main/java/io/metersphere/track/controller/IssueCommentController.java create mode 100644 backend/src/main/java/io/metersphere/track/dto/IssueCommentDTO.java create mode 100644 backend/src/main/java/io/metersphere/track/request/issues/SaveIssueCommentRequest.java create mode 100644 backend/src/main/java/io/metersphere/track/service/IssueCommentService.java create mode 100644 backend/src/main/resources/mail/track/IssuesCommentUpdate.html create mode 100644 frontend/src/business/components/track/issue/IssueComment.vue diff --git a/backend/src/main/java/io/metersphere/base/domain/IssueComment.java b/backend/src/main/java/io/metersphere/base/domain/IssueComment.java new file mode 100644 index 0000000000..e498e1e11c --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/domain/IssueComment.java @@ -0,0 +1,23 @@ +package io.metersphere.base.domain; + +import java.io.Serializable; +import lombok.Data; + +@Data +public class IssueComment implements Serializable { + private String id; + + private String issueId; + + private String author; + + private Long createTime; + + private Long updateTime; + + private String status; + + private String description; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/domain/IssueCommentExample.java b/backend/src/main/java/io/metersphere/base/domain/IssueCommentExample.java new file mode 100644 index 0000000000..2acd31cb8b --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/domain/IssueCommentExample.java @@ -0,0 +1,600 @@ +package io.metersphere.base.domain; + +import java.util.ArrayList; +import java.util.List; + +public class IssueCommentExample { + protected String orderByClause; + + protected boolean distinct; + + protected List oredCriteria; + + public IssueCommentExample() { + oredCriteria = new ArrayList(); + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isDistinct() { + return distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andIdIsNull() { + addCriterion("id is null"); + return (Criteria) this; + } + + public Criteria andIdIsNotNull() { + addCriterion("id is not null"); + return (Criteria) this; + } + + public Criteria andIdEqualTo(String value) { + addCriterion("id =", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotEqualTo(String value) { + addCriterion("id <>", value, "id"); + return (Criteria) this; + } + + public Criteria andIdGreaterThan(String value) { + addCriterion("id >", value, "id"); + return (Criteria) this; + } + + public Criteria andIdGreaterThanOrEqualTo(String value) { + addCriterion("id >=", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLessThan(String value) { + addCriterion("id <", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLessThanOrEqualTo(String value) { + addCriterion("id <=", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLike(String value) { + addCriterion("id like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotLike(String value) { + addCriterion("id not like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdIn(List values) { + addCriterion("id in", values, "id"); + return (Criteria) this; + } + + public Criteria andIdNotIn(List values) { + addCriterion("id not in", values, "id"); + return (Criteria) this; + } + + public Criteria andIdBetween(String value1, String value2) { + addCriterion("id between", value1, value2, "id"); + return (Criteria) this; + } + + public Criteria andIdNotBetween(String value1, String value2) { + addCriterion("id not between", value1, value2, "id"); + return (Criteria) this; + } + + public Criteria andIssueIdIsNull() { + addCriterion("issue_id is null"); + return (Criteria) this; + } + + public Criteria andIssueIdIsNotNull() { + addCriterion("issue_id is not null"); + return (Criteria) this; + } + + public Criteria andIssueIdEqualTo(String value) { + addCriterion("issue_id =", value, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdNotEqualTo(String value) { + addCriterion("issue_id <>", value, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdGreaterThan(String value) { + addCriterion("issue_id >", value, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdGreaterThanOrEqualTo(String value) { + addCriterion("issue_id >=", value, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdLessThan(String value) { + addCriterion("issue_id <", value, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdLessThanOrEqualTo(String value) { + addCriterion("issue_id <=", value, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdLike(String value) { + addCriterion("issue_id like", value, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdNotLike(String value) { + addCriterion("issue_id not like", value, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdIn(List values) { + addCriterion("issue_id in", values, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdNotIn(List values) { + addCriterion("issue_id not in", values, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdBetween(String value1, String value2) { + addCriterion("issue_id between", value1, value2, "issueId"); + return (Criteria) this; + } + + public Criteria andIssueIdNotBetween(String value1, String value2) { + addCriterion("issue_id not between", value1, value2, "issueId"); + return (Criteria) this; + } + + public Criteria andAuthorIsNull() { + addCriterion("author is null"); + return (Criteria) this; + } + + public Criteria andAuthorIsNotNull() { + addCriterion("author is not null"); + return (Criteria) this; + } + + public Criteria andAuthorEqualTo(String value) { + addCriterion("author =", value, "author"); + return (Criteria) this; + } + + public Criteria andAuthorNotEqualTo(String value) { + addCriterion("author <>", value, "author"); + return (Criteria) this; + } + + public Criteria andAuthorGreaterThan(String value) { + addCriterion("author >", value, "author"); + return (Criteria) this; + } + + public Criteria andAuthorGreaterThanOrEqualTo(String value) { + addCriterion("author >=", value, "author"); + return (Criteria) this; + } + + public Criteria andAuthorLessThan(String value) { + addCriterion("author <", value, "author"); + return (Criteria) this; + } + + public Criteria andAuthorLessThanOrEqualTo(String value) { + addCriterion("author <=", value, "author"); + return (Criteria) this; + } + + public Criteria andAuthorLike(String value) { + addCriterion("author like", value, "author"); + return (Criteria) this; + } + + public Criteria andAuthorNotLike(String value) { + addCriterion("author not like", value, "author"); + return (Criteria) this; + } + + public Criteria andAuthorIn(List values) { + addCriterion("author in", values, "author"); + return (Criteria) this; + } + + public Criteria andAuthorNotIn(List values) { + addCriterion("author not in", values, "author"); + return (Criteria) this; + } + + public Criteria andAuthorBetween(String value1, String value2) { + addCriterion("author between", value1, value2, "author"); + return (Criteria) this; + } + + public Criteria andAuthorNotBetween(String value1, String value2) { + addCriterion("author not between", value1, value2, "author"); + return (Criteria) this; + } + + public Criteria andCreateTimeIsNull() { + addCriterion("create_time is null"); + return (Criteria) this; + } + + public Criteria andCreateTimeIsNotNull() { + addCriterion("create_time is not null"); + return (Criteria) this; + } + + public Criteria andCreateTimeEqualTo(Long value) { + addCriterion("create_time =", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotEqualTo(Long value) { + addCriterion("create_time <>", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeGreaterThan(Long value) { + addCriterion("create_time >", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeGreaterThanOrEqualTo(Long value) { + addCriterion("create_time >=", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeLessThan(Long value) { + addCriterion("create_time <", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeLessThanOrEqualTo(Long value) { + addCriterion("create_time <=", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeIn(List values) { + addCriterion("create_time in", values, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotIn(List values) { + addCriterion("create_time not in", values, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeBetween(Long value1, Long value2) { + addCriterion("create_time between", value1, value2, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotBetween(Long value1, Long value2) { + addCriterion("create_time not between", value1, value2, "createTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIsNull() { + addCriterion("update_time is null"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIsNotNull() { + addCriterion("update_time is not null"); + return (Criteria) this; + } + + public Criteria andUpdateTimeEqualTo(Long value) { + addCriterion("update_time =", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotEqualTo(Long value) { + addCriterion("update_time <>", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeGreaterThan(Long value) { + addCriterion("update_time >", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeGreaterThanOrEqualTo(Long value) { + addCriterion("update_time >=", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeLessThan(Long value) { + addCriterion("update_time <", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeLessThanOrEqualTo(Long value) { + addCriterion("update_time <=", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIn(List values) { + addCriterion("update_time in", values, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotIn(List values) { + addCriterion("update_time not in", values, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeBetween(Long value1, Long value2) { + addCriterion("update_time between", value1, value2, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotBetween(Long value1, Long value2) { + addCriterion("update_time not between", value1, value2, "updateTime"); + return (Criteria) this; + } + + public Criteria andStatusIsNull() { + addCriterion("`status` is null"); + return (Criteria) this; + } + + public Criteria andStatusIsNotNull() { + addCriterion("`status` is not null"); + return (Criteria) this; + } + + public Criteria andStatusEqualTo(String value) { + addCriterion("`status` =", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusNotEqualTo(String value) { + addCriterion("`status` <>", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusGreaterThan(String value) { + addCriterion("`status` >", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusGreaterThanOrEqualTo(String value) { + addCriterion("`status` >=", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusLessThan(String value) { + addCriterion("`status` <", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusLessThanOrEqualTo(String value) { + addCriterion("`status` <=", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusLike(String value) { + addCriterion("`status` like", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusNotLike(String value) { + addCriterion("`status` not like", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusIn(List values) { + addCriterion("`status` in", values, "status"); + return (Criteria) this; + } + + public Criteria andStatusNotIn(List values) { + addCriterion("`status` not in", values, "status"); + return (Criteria) this; + } + + public Criteria andStatusBetween(String value1, String value2) { + addCriterion("`status` between", value1, value2, "status"); + return (Criteria) this; + } + + public Criteria andStatusNotBetween(String value1, String value2) { + addCriterion("`status` not between", value1, value2, "status"); + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + + protected Criteria() { + super(); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/IssueCommentMapper.java b/backend/src/main/java/io/metersphere/base/mapper/IssueCommentMapper.java new file mode 100644 index 0000000000..fd6f96ecd6 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/IssueCommentMapper.java @@ -0,0 +1,36 @@ +package io.metersphere.base.mapper; + +import io.metersphere.base.domain.IssueComment; +import io.metersphere.base.domain.IssueCommentExample; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +public interface IssueCommentMapper { + long countByExample(IssueCommentExample example); + + int deleteByExample(IssueCommentExample example); + + int deleteByPrimaryKey(String id); + + int insert(IssueComment record); + + int insertSelective(IssueComment record); + + List selectByExampleWithBLOBs(IssueCommentExample example); + + List selectByExample(IssueCommentExample example); + + IssueComment selectByPrimaryKey(String id); + + int updateByExampleSelective(@Param("record") IssueComment record, @Param("example") IssueCommentExample example); + + int updateByExampleWithBLOBs(@Param("record") IssueComment record, @Param("example") IssueCommentExample example); + + int updateByExample(@Param("record") IssueComment record, @Param("example") IssueCommentExample example); + + int updateByPrimaryKeySelective(IssueComment record); + + int updateByPrimaryKeyWithBLOBs(IssueComment record); + + int updateByPrimaryKey(IssueComment record); +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/IssueCommentMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/IssueCommentMapper.xml new file mode 100644 index 0000000000..f0676da789 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/IssueCommentMapper.xml @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + id, issue_id, author, create_time, update_time, `status` + + + description + + + + + + delete from issue_comment + where id = #{id,jdbcType=VARCHAR} + + + delete from issue_comment + + + + + + insert into issue_comment (id, issue_id, author, + create_time, update_time, `status`, + description) + values (#{id,jdbcType=VARCHAR}, #{issueId,jdbcType=VARCHAR}, #{author,jdbcType=VARCHAR}, + #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{status,jdbcType=VARCHAR}, + #{description,jdbcType=LONGVARCHAR}) + + + insert into issue_comment + + + id, + + + issue_id, + + + author, + + + create_time, + + + update_time, + + + `status`, + + + description, + + + + + #{id,jdbcType=VARCHAR}, + + + #{issueId,jdbcType=VARCHAR}, + + + #{author,jdbcType=VARCHAR}, + + + #{createTime,jdbcType=BIGINT}, + + + #{updateTime,jdbcType=BIGINT}, + + + #{status,jdbcType=VARCHAR}, + + + #{description,jdbcType=LONGVARCHAR}, + + + + + + update issue_comment + + + id = #{record.id,jdbcType=VARCHAR}, + + + issue_id = #{record.issueId,jdbcType=VARCHAR}, + + + author = #{record.author,jdbcType=VARCHAR}, + + + create_time = #{record.createTime,jdbcType=BIGINT}, + + + update_time = #{record.updateTime,jdbcType=BIGINT}, + + + `status` = #{record.status,jdbcType=VARCHAR}, + + + description = #{record.description,jdbcType=LONGVARCHAR}, + + + + + + + + update issue_comment + set id = #{record.id,jdbcType=VARCHAR}, + issue_id = #{record.issueId,jdbcType=VARCHAR}, + author = #{record.author,jdbcType=VARCHAR}, + create_time = #{record.createTime,jdbcType=BIGINT}, + update_time = #{record.updateTime,jdbcType=BIGINT}, + `status` = #{record.status,jdbcType=VARCHAR}, + description = #{record.description,jdbcType=LONGVARCHAR} + + + + + + update issue_comment + set id = #{record.id,jdbcType=VARCHAR}, + issue_id = #{record.issueId,jdbcType=VARCHAR}, + author = #{record.author,jdbcType=VARCHAR}, + create_time = #{record.createTime,jdbcType=BIGINT}, + update_time = #{record.updateTime,jdbcType=BIGINT}, + `status` = #{record.status,jdbcType=VARCHAR} + + + + + + update issue_comment + + + issue_id = #{issueId,jdbcType=VARCHAR}, + + + author = #{author,jdbcType=VARCHAR}, + + + create_time = #{createTime,jdbcType=BIGINT}, + + + update_time = #{updateTime,jdbcType=BIGINT}, + + + `status` = #{status,jdbcType=VARCHAR}, + + + description = #{description,jdbcType=LONGVARCHAR}, + + + where id = #{id,jdbcType=VARCHAR} + + + update issue_comment + set issue_id = #{issueId,jdbcType=VARCHAR}, + author = #{author,jdbcType=VARCHAR}, + create_time = #{createTime,jdbcType=BIGINT}, + update_time = #{updateTime,jdbcType=BIGINT}, + `status` = #{status,jdbcType=VARCHAR}, + description = #{description,jdbcType=LONGVARCHAR} + where id = #{id,jdbcType=VARCHAR} + + + update issue_comment + set issue_id = #{issueId,jdbcType=VARCHAR}, + author = #{author,jdbcType=VARCHAR}, + create_time = #{createTime,jdbcType=BIGINT}, + update_time = #{updateTime,jdbcType=BIGINT}, + `status` = #{status,jdbcType=VARCHAR} + where id = #{id,jdbcType=VARCHAR} + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssueCommentMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssueCommentMapper.java new file mode 100644 index 0000000000..79efb1b525 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssueCommentMapper.java @@ -0,0 +1,18 @@ +package io.metersphere.base.mapper.ext; + +import io.metersphere.track.dto.IssueCommentDTO; +import io.metersphere.track.dto.TestCaseCommentDTO; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ExtIssueCommentMapper { + + /** + * 获取用例的评论 + * @param issueId + * @return + */ + List getComments(@Param("issueId") String issueId); + +} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssueCommentMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssueCommentMapper.xml new file mode 100644 index 0000000000..f44207354d --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssueCommentMapper.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/track/controller/IssueCommentController.java b/backend/src/main/java/io/metersphere/track/controller/IssueCommentController.java new file mode 100644 index 0000000000..64092d8d73 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/controller/IssueCommentController.java @@ -0,0 +1,56 @@ +package io.metersphere.track.controller; + +import io.metersphere.base.domain.IssueComment; +import io.metersphere.commons.constants.NoticeConstants; +import io.metersphere.commons.constants.OperLogConstants; +import io.metersphere.commons.constants.PermissionConstants; +import io.metersphere.log.annotation.MsAuditLog; +import io.metersphere.notice.annotation.SendNotice; +import io.metersphere.track.dto.IssueCommentDTO; +import io.metersphere.track.request.issues.IssuesRelevanceRequest; +import io.metersphere.track.request.issues.SaveIssueCommentRequest; +import io.metersphere.track.service.IssueCommentService; +import io.metersphere.track.service.IssuesService; +import io.metersphere.track.service.TestCaseCommentService; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.UUID; + +@RequestMapping("/issues/comment") +@RestController +public class IssueCommentController { + + @Resource + private IssueCommentService issueCommentService; + + @PostMapping("/save") + @RequiresPermissions(PermissionConstants.PROJECT_TRACK_REVIEW_READ_COMMENT) + @SendNotice(taskType = NoticeConstants.TaskType.DEFECT_TASK, target = "#targetClass.get(#request.issuesId)", targetClass = IssuesService.class, + event = NoticeConstants.Event.COMMENT, mailTemplate = "track/IssuesCommentUpdate", subject = "缺陷评论更新通知") + public IssueComment saveComment(@RequestBody IssuesRelevanceRequest request) { + request.setId(UUID.randomUUID().toString()); + return issueCommentService.saveComment(request); + } + + @GetMapping("/list/{issueId}") + public List getComments(@PathVariable String issueId) { + return issueCommentService.getComments(issueId); + } + + @GetMapping("/delete/{commentId}") + @RequiresPermissions(PermissionConstants.PROJECT_TRACK_REVIEW_READ_COMMENT) + @MsAuditLog(module = "track_bug", type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#commentId)", msClass = TestCaseCommentService.class) + public void deleteComment(@PathVariable String commentId) { + issueCommentService.delete(commentId); + } + + @PostMapping("/edit") + @RequiresPermissions(PermissionConstants.PROJECT_TRACK_REVIEW_READ_COMMENT) + @MsAuditLog(module = "track_bug", type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#request.id)", content = "#msClass.getLogDetails(#request.id)", msClass = TestCaseCommentService.class) + public IssueComment editComment(@RequestBody SaveIssueCommentRequest request) { + return issueCommentService.edit(request); + } +} diff --git a/backend/src/main/java/io/metersphere/track/controller/IssuesController.java b/backend/src/main/java/io/metersphere/track/controller/IssuesController.java index 2b8bb3dd6f..6c0c7e095a 100644 --- a/backend/src/main/java/io/metersphere/track/controller/IssuesController.java +++ b/backend/src/main/java/io/metersphere/track/controller/IssuesController.java @@ -150,4 +150,5 @@ public class IssuesController { public IssueTemplateDao getThirdPartTemplate(@PathVariable String projectId) { return issuesService.getThirdPartTemplate(projectId); } + } diff --git a/backend/src/main/java/io/metersphere/track/dto/IssueCommentDTO.java b/backend/src/main/java/io/metersphere/track/dto/IssueCommentDTO.java new file mode 100644 index 0000000000..82c8857f92 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/dto/IssueCommentDTO.java @@ -0,0 +1,9 @@ +package io.metersphere.track.dto; + +import io.metersphere.base.domain.IssueComment; +import lombok.Data; + +@Data +public class IssueCommentDTO extends IssueComment { + private String authorName; +} diff --git a/backend/src/main/java/io/metersphere/track/request/issues/IssuesRelevanceRequest.java b/backend/src/main/java/io/metersphere/track/request/issues/IssuesRelevanceRequest.java index deac7ff00d..0bcb7be9c4 100644 --- a/backend/src/main/java/io/metersphere/track/request/issues/IssuesRelevanceRequest.java +++ b/backend/src/main/java/io/metersphere/track/request/issues/IssuesRelevanceRequest.java @@ -10,6 +10,8 @@ import java.util.List; @Getter @Setter public class IssuesRelevanceRequest { + private String id; + /** * 缺陷ID */ @@ -30,4 +32,6 @@ public class IssuesRelevanceRequest { private List issueIds; private Boolean checked; + + private String description; } diff --git a/backend/src/main/java/io/metersphere/track/request/issues/SaveIssueCommentRequest.java b/backend/src/main/java/io/metersphere/track/request/issues/SaveIssueCommentRequest.java new file mode 100644 index 0000000000..fc4dbfd274 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/request/issues/SaveIssueCommentRequest.java @@ -0,0 +1,11 @@ +package io.metersphere.track.request.issues; + +import io.metersphere.base.domain.IssueComment; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SaveIssueCommentRequest extends IssueComment { + private String reviewId; +} diff --git a/backend/src/main/java/io/metersphere/track/service/IssueCommentService.java b/backend/src/main/java/io/metersphere/track/service/IssueCommentService.java new file mode 100644 index 0000000000..c95c000568 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/service/IssueCommentService.java @@ -0,0 +1,80 @@ +package io.metersphere.track.service; + +import com.alibaba.fastjson.JSON; +import io.metersphere.base.domain.IssueComment; +import io.metersphere.base.domain.Issues; +import io.metersphere.base.mapper.IssueCommentMapper; +import io.metersphere.base.mapper.IssuesMapper; +import io.metersphere.base.mapper.ext.ExtIssueCommentMapper; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.i18n.Translator; +import io.metersphere.log.utils.ReflexObjectUtil; +import io.metersphere.log.vo.DetailColumn; +import io.metersphere.log.vo.OperatingLogDetails; +import io.metersphere.log.vo.track.TestCaseReviewReference; +import io.metersphere.track.dto.IssueCommentDTO; +import io.metersphere.track.request.issues.IssuesRelevanceRequest; +import io.metersphere.track.request.issues.SaveIssueCommentRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +@Service +@Transactional(rollbackFor = Exception.class) +public class IssueCommentService { + @Resource + private IssuesMapper issuesMapper; + @Resource + private ExtIssueCommentMapper extIssueCommentMapper; + @Resource + private IssueCommentMapper issueCommentMapper; + + public IssueComment saveComment(IssuesRelevanceRequest request) { + IssueComment issueComment = new IssueComment(); + issueComment.setId(request.getId()); + issueComment.setAuthor(SessionUtils.getUser().getId()); + issueComment.setIssueId(request.getIssuesId()); + issueComment.setCreateTime(System.currentTimeMillis()); + issueComment.setUpdateTime(System.currentTimeMillis()); + issueComment.setDescription(request.getDescription()); + issueCommentMapper.insert(issueComment); + return issueComment; + } + + public List getComments(String caseId) { + return extIssueCommentMapper.getComments(caseId); + } + + public void delete(String commentId) { + checkCommentOwner(commentId); + issueCommentMapper.deleteByPrimaryKey(commentId); + } + + public IssueComment edit(SaveIssueCommentRequest request) { + checkCommentOwner(request.getId()); + issueCommentMapper.updateByPrimaryKeySelective(request); + return issueCommentMapper.selectByPrimaryKey(request.getId()); + } + + private void checkCommentOwner(String commentId) { + IssueComment comment = issueCommentMapper.selectByPrimaryKey(commentId); + if (!StringUtils.equals(comment.getAuthor(), SessionUtils.getUser().getId())) { + MSException.throwException(Translator.get("check_owner_comment")); + } + } + + public String getLogDetails(String id) { + IssueComment issueComment = issueCommentMapper.selectByPrimaryKey(id); + if (issueComment != null) { + Issues review = issuesMapper.selectByPrimaryKey(issueComment.getIssueId()); + List columns = ReflexObjectUtil.getColumns(issueComment, TestCaseReviewReference.commentReviewColumns); + OperatingLogDetails details = new OperatingLogDetails(JSON.toJSONString(issueComment.getId()), review.getProjectId(), issueComment.getDescription(), issueComment.getAuthor(), columns); + return JSON.toJSONString(details); + } + return null; + } +} diff --git a/backend/src/main/java/io/metersphere/track/service/IssuesService.java b/backend/src/main/java/io/metersphere/track/service/IssuesService.java index 126bcaa910..e743b26b7d 100644 --- a/backend/src/main/java/io/metersphere/track/service/IssuesService.java +++ b/backend/src/main/java/io/metersphere/track/service/IssuesService.java @@ -64,8 +64,6 @@ public class IssuesService { @Resource private TestCaseIssuesMapper testCaseIssuesMapper; @Resource - private IssueTemplateMapper issueTemplateMapper; - @Resource private ExtIssuesMapper extIssuesMapper; @Resource private CustomFieldTemplateService customFieldTemplateService; @@ -111,6 +109,7 @@ public class IssuesService { //saveFollows(issuesRequest.getId(), issuesRequest.getFollows()); // todo 缺陷更新事件? } + public void saveFollows(String issueId, List follows) { IssueFollowExample example = new IssueFollowExample(); example.createCriteria().andIssueIdEqualTo(issueId); @@ -124,6 +123,7 @@ public class IssuesService { } } } + public List getAddPlatforms(IssuesUpdateRequest updateRequest) { List platforms = new ArrayList<>(); if (StringUtils.isNotBlank(updateRequest.getTestCaseId())) { @@ -353,7 +353,7 @@ public class IssuesService { .collect(Collectors.toList()); item.setCaseIds(caseIds); item.setCaseCount(testCaseIssues.size()); - if(StringUtils.equals(item.getPlatform(),"Tapd")){ + if (StringUtils.equals(item.getPlatform(), "Tapd")) { TapdPlatform platform = (TapdPlatform) IssueFactory.createPlatform(item.getPlatform(), request); List tapdUsers = platform.getTapdUsers(item.getProjectId(), item.getPlatformId()); item.setTapdUsers(tapdUsers); @@ -469,6 +469,7 @@ public class IssuesService { /** * 获取默认的自定义字段的取值,同步之后更新成第三方平台的值 + * * @param projectId * @return */ @@ -606,7 +607,7 @@ public class IssuesService { issuesMapper.updateByPrimaryKeySelective(issues); } - public List getCountByStatus(IssuesRequest request){ + public List getCountByStatus(IssuesRequest request) { return extIssuesMapper.getCountByStatus(request); } @@ -619,7 +620,7 @@ public class IssuesService { IssueFollowExample example = new IssueFollowExample(); example.createCriteria().andIssueIdEqualTo(issueId); List follows = issueFollowMapper.selectByExample(example); - if(follows==null||follows.size()==0){ + if (follows == null || follows.size() == 0) { return result; } result = follows.stream().map(IssueFollow::getFollowId).distinct().collect(Collectors.toList()); @@ -635,6 +636,7 @@ public class IssuesService { return issuesMapper.selectByExampleWithBLOBs(example); } + public IssueTemplateDao getThirdPartTemplate(String projectId) { if (StringUtils.isNotBlank(projectId)) { Project project = projectService.getProjectById(projectId); diff --git a/backend/src/main/resources/db/migration/V103__v1.16_release.sql b/backend/src/main/resources/db/migration/V103__v1.16_release.sql index 4be971e034..40ea3ea2fa 100644 --- a/backend/src/main/resources/db/migration/V103__v1.16_release.sql +++ b/backend/src/main/resources/db/migration/V103__v1.16_release.sql @@ -65,6 +65,19 @@ CREATE TABLE IF NOT EXISTS `api_scenario_report_structure` INSERT INTO user_group_permission (id, group_id, permission_id, module_id) VALUES (UUID(), 'project_app_manager', 'PROJECT_APP_MANAGER:READ+EDIT', 'PROJECT_APP_MANAGER'); +CREATE TABLE `issue_comment` +( + `id` varchar(64) NOT NULL, + `issue_id` varchar(64) DEFAULT NULL, + `description` text, + `author` varchar(50) DEFAULT NULL, + `create_time` bigint(13) DEFAULT NULL, + `update_time` bigint(13) DEFAULT NULL, + `status` varchar(80) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8; + CREATE TABLE IF NOT EXISTS `api_execution_queue` ( `id` varchar(50) NOT NULL COMMENT 'ID', @@ -90,4 +103,5 @@ CREATE TABLE IF NOT EXISTS `api_execution_queue_detail` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; ALTER TABLE load_test - MODIFY name VARCHAR(255) NOT NULL COMMENT 'Test name'; \ No newline at end of file + MODIFY name VARCHAR(255) NOT NULL COMMENT 'Test name'; + diff --git a/backend/src/main/resources/mail/track/IssuesCommentUpdate.html b/backend/src/main/resources/mail/track/IssuesCommentUpdate.html new file mode 100644 index 0000000000..8169964b7b --- /dev/null +++ b/backend/src/main/resources/mail/track/IssuesCommentUpdate.html @@ -0,0 +1,12 @@ + + + + + MeterSphere + + +
+

${operator}更新了缺陷评论: ${title}

+
+ + \ No newline at end of file diff --git a/frontend/src/business/components/settings/workspace/components/track/DefectTaskNotification.vue b/frontend/src/business/components/settings/workspace/components/track/DefectTaskNotification.vue index afee25f8b5..586bae265d 100644 --- a/frontend/src/business/components/settings/workspace/components/track/DefectTaskNotification.vue +++ b/frontend/src/business/components/settings/workspace/components/track/DefectTaskNotification.vue @@ -174,6 +174,7 @@ export default { {value: 'CREATE', label: this.$t('commons.create')}, {value: 'UPDATE', label: this.$t('commons.update')}, {value: 'DELETE', label: this.$t('commons.delete')}, + {value: 'COMMENT', label: this.$t('commons.comment')}, ], variables: [ { @@ -378,6 +379,14 @@ export default { } } break; + case "COMMENT": + testPlanReceivers.unshift({id: 'CREATOR', name: this.$t('commons.create_user')}); + if (row.isSet) { + if (i2 < 0) { + row.userIds.unshift('CREATOR'); + } + } + break; default: break; } diff --git a/frontend/src/business/components/track/case/components/TestCaseEdit.vue b/frontend/src/business/components/track/case/components/TestCaseEdit.vue index 9dc1f7d400..429dffa8a3 100644 --- a/frontend/src/business/components/track/case/components/TestCaseEdit.vue +++ b/frontend/src/business/components/track/case/components/TestCaseEdit.vue @@ -411,6 +411,7 @@ export default { } }, methods: { + alert:alert, currentUser: () => { return getCurrentUser(); }, diff --git a/frontend/src/business/components/track/issue/IssueComment.vue b/frontend/src/business/components/track/issue/IssueComment.vue new file mode 100644 index 0000000000..c1a4a652dc --- /dev/null +++ b/frontend/src/business/components/track/issue/IssueComment.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/frontend/src/business/components/track/issue/IssueEditDetail.vue b/frontend/src/business/components/track/issue/IssueEditDetail.vue index 1adf60b3d4..ccc408e353 100644 --- a/frontend/src/business/components/track/issue/IssueEditDetail.vue +++ b/frontend/src/business/components/track/issue/IssueEditDetail.vue @@ -66,6 +66,31 @@ ref="testCaseIssueList"/> + + + + {{ $t('test_track.review.comment') }}: + + + + + + + +
+ + + {{ $t('test_track.comment.no_comment') }} + + +
+
+
+ @@ -92,6 +117,9 @@ import { } from "@/common/js/utils"; import {enableThirdPartTemplate, getIssuePartTemplateWithProject} from "@/network/Issue"; import CustomFiledFormItem from "@/business/components/common/components/form/CustomFiledFormItem"; +import MsMarkDownText from "@/business/components/track/case/components/MsMarkDownText"; +import IssueComment from "@/business/components/track/issue/IssueComment"; +import ReviewCommentItem from "@/business/components/track/review/commom/ReviewCommentItem"; export default { name: "IssueEditDetail", @@ -104,10 +132,14 @@ export default { CustomFieldRelateList, CustomFieldFormList, MsFormDivider, - TemplateComponentEditHeader + TemplateComponentEditHeader, + MsMarkDownText, + IssueComment, + ReviewCommentItem }, data() { return { + type: null, issueId:'', result: { loading: false @@ -133,13 +165,50 @@ export default { title: '', description: '', creator: null, + remark: null }, tapdUsers: [], zentaoUsers: [], Builds: [], hasTapdId: false, hasZentaoId: false, - currentProject: null + currentProject: null, + toolbars: { + bold: false, // 粗体 + italic: false, // 斜体 + header: false, // 标题 + underline: false, // 下划线 + strikethrough: false, // 中划线 + mark: false, // 标记 + superscript: false, // 上角标 + subscript: false, // 下角标 + quote: false, // 引用 + ol: false, // 有序列表 + ul: false, // 无序列表 + link: false, // 链接 + imagelink: true, // 图片链接 + code: false, // code + table: false, // 表格 + fullscreen: false, // 全屏编辑 + readmodel: false, // 沉浸式阅读 + htmlcode: false, // 展示html源码 + help: false, // 帮助 + /* 1.3.5 */ + undo: false, // 上一步 + redo: false, // 下一步 + trash: false, // 清空 + save: false, // 保存(触发events中的save事件) + /* 1.4.2 */ + navigation: false, // 导航目录 + /* 2.1.8 */ + alignleft: false, // 左对齐 + aligncenter: false, // 居中 + alignright: false, // 右对齐 + /* 2.2.1 */ + subfield: false, // 单双栏模式 + preview: false, // 预览 + }, + comments: [] }; }, props: { @@ -259,6 +328,7 @@ export default { if (this.$refs.testCaseIssueList) { this.$refs.testCaseIssueList.initTableData(); } + this.getComments(); }); }, save() { @@ -349,6 +419,16 @@ export default { }); } } + }, + openComment() { + this.$refs.issueComment.open(); + }, + getComments() { + if (this.issueId) { + this.result = this.$get('/issues/comment/list/' + this.issueId, res => { + this.comments = res.data; + }) + } } } }; diff --git a/frontend/src/business/components/track/issue/IssueList.vue b/frontend/src/business/components/track/issue/IssueList.vue index e93ef64bb1..07c24748f4 100644 --- a/frontend/src/business/components/track/issue/IssueList.vue +++ b/frontend/src/business/components/track/issue/IssueList.vue @@ -1,6 +1,7 @@