
pull-request-comment: Changes the commit comment reply sorting
@65c08aa055f4a70bb8fe094077728758dc0183be
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -2734,7 +2734,6 @@ |
2734 | 2734 |
|
2735 | 2735 |
.yobicon-paperclip {vertical-align:middle;margin-top: 4px;} |
2736 | 2736 |
|
2737 |
- |
|
2738 | 2737 |
} |
2739 | 2738 |
.comment-body { |
2740 | 2739 |
font-size: 12px; |
... | ... | @@ -2749,6 +2748,7 @@ |
2749 | 2748 |
.border-radius(2px); |
2750 | 2749 |
} |
2751 | 2750 |
} |
2751 |
+ |
|
2752 | 2752 |
} |
2753 | 2753 |
.attach-preview-wrap { |
2754 | 2754 |
margin-top: 10px; |
... | ... | @@ -2762,8 +2762,32 @@ |
2762 | 2762 |
.meta-info { background-color:#f8f8f8; } |
2763 | 2763 |
|
2764 | 2764 |
.attach { background-color:#f8f8f8; } |
2765 |
- } |
|
2765 |
+ } |
|
2766 |
+ } |
|
2766 | 2767 |
|
2768 |
+ .reply { |
|
2769 |
+ padding:0 20px; |
|
2770 |
+ .media-body { |
|
2771 |
+ border-top:0px; |
|
2772 |
+ .border-radius(0 0 0 0); |
|
2773 |
+ &:before { |
|
2774 |
+ content:none; |
|
2775 |
+ } |
|
2776 |
+ } |
|
2777 |
+ } |
|
2778 |
+ |
|
2779 |
+ .hasReply { |
|
2780 |
+ padding-bottom:0px; |
|
2781 |
+ .media-body { |
|
2782 |
+ .border-radius(3px 3px 0 0); |
|
2783 |
+ } |
|
2784 |
+ } |
|
2785 |
+ |
|
2786 |
+ .last { |
|
2787 |
+ padding-bottom:10px; |
|
2788 |
+ .media-body { |
|
2789 |
+ .border-radius(0 0 3px 3px); |
|
2790 |
+ } |
|
2767 | 2791 |
} |
2768 | 2792 |
|
2769 | 2793 |
.event { |
... | ... | @@ -2771,7 +2795,6 @@ |
2771 | 2795 |
padding:15px 0 15px 75px; |
2772 | 2796 |
font-size:14px; |
2773 | 2797 |
line-height:14px; |
2774 |
- |
|
2775 | 2798 |
.state { |
2776 | 2799 |
color:#FFF; |
2777 | 2800 |
padding: 6px 0; |
... | ... | @@ -4585,4 +4608,4 @@ |
4585 | 4608 |
|
4586 | 4609 |
div.diff-body[data-outdated="true"] tr:hover .icon-comment { |
4587 | 4610 |
visibility: hidden; |
4588 |
-}(No newline at end of file) |
|
4611 |
+} |
--- app/models/CommitComment.java
+++ app/models/CommitComment.java
... | ... | @@ -14,6 +14,8 @@ |
14 | 14 |
private static final long serialVersionUID = 1L; |
15 | 15 |
public static final Finder<Long, CommitComment> find = new Finder<>(Long.class, CommitComment.class); |
16 | 16 |
|
17 |
+ public List<CommitComment> replies = new ArrayList<>(); |
|
18 |
+ |
|
17 | 19 |
public CommitComment() { |
18 | 20 |
super(); |
19 | 21 |
} |
... | ... | @@ -80,4 +82,16 @@ |
80 | 82 |
} |
81 | 83 |
return list; |
82 | 84 |
} |
85 |
+ |
|
86 |
+ /** |
|
87 |
+ * CommitComment의 groupKey를 반환한다. |
|
88 |
+ * commitId, path, line정보를 조한한 키가 일치할 경우 동일한 내용에 대한 |
|
89 |
+ * 코멘트로 간주한다. |
|
90 |
+ * |
|
91 |
+ * @return |
|
92 |
+ */ |
|
93 |
+ public String groupKey() { |
|
94 |
+ return new StringBuilder().append(this.commitId) |
|
95 |
+ .append(this.path).append(this.line).toString(); |
|
96 |
+ } |
|
83 | 97 |
} |
--- app/models/PullRequest.java
+++ app/models/PullRequest.java
... | ... | @@ -11,31 +11,15 @@ |
11 | 11 |
import models.resource.Resource; |
12 | 12 |
import models.resource.ResourceConvertible; |
13 | 13 |
|
14 |
-import org.apache.commons.lang.ArrayUtils; |
|
15 | 14 |
import org.apache.commons.lang3.StringUtils; |
16 | 15 |
import org.eclipse.jgit.api.MergeResult; |
17 | 16 |
import org.eclipse.jgit.api.errors.GitAPIException; |
18 |
-import org.eclipse.jgit.lib.ObjectId; |
|
19 | 17 |
import org.eclipse.jgit.lib.Repository; |
20 | 18 |
import org.eclipse.jgit.lib.RepositoryBuilder; |
21 | 19 |
|
22 |
-import org.joda.time.DateTimeConstants; |
|
23 |
-import org.eclipse.jgit.diff.*; |
|
24 |
-import org.eclipse.jgit.lib.ConfigConstants; |
|
25 |
-import org.eclipse.jgit.lib.ObjectId; |
|
26 |
-import org.eclipse.jgit.lib.Repository; |
|
27 |
-import org.eclipse.jgit.revwalk.RevTree; |
|
28 |
-import org.eclipse.jgit.revwalk.RevWalk; |
|
29 |
-import org.eclipse.jgit.treewalk.TreeWalk; |
|
30 |
-import org.eclipse.jgit.util.io.NullOutputStream; |
|
31 | 20 |
import org.joda.time.Duration; |
32 | 21 |
|
33 |
-import com.avaje.ebean.Ebean; |
|
34 | 22 |
import com.avaje.ebean.Expr; |
35 |
-import com.avaje.ebean.RawSql; |
|
36 |
-import com.avaje.ebean.RawSqlBuilder; |
|
37 |
-import com.avaje.ebean.SqlQuery; |
|
38 |
-import com.avaje.ebean.SqlRow; |
|
39 | 23 |
|
40 | 24 |
import play.data.validation.Constraints; |
41 | 25 |
import play.db.ebean.Model; |
... | ... | @@ -48,29 +32,14 @@ |
48 | 32 |
import playRepository.GitRepository.CloneAndFetch; |
49 | 33 |
import utils.AccessControl; |
50 | 34 |
import utils.Constants; |
51 |
-import utils.GravatarUtil; |
|
52 | 35 |
import utils.JodaDateUtil; |
53 | 36 |
import utils.WatchService; |
54 | 37 |
|
55 | 38 |
import javax.persistence.*; |
56 | 39 |
import javax.validation.constraints.Size; |
57 | 40 |
|
41 |
+import java.io.IOException; |
|
58 | 42 |
import java.util.*; |
59 |
-import java.util.regex.Matcher; |
|
60 |
- |
|
61 |
-import java.io.IOException; |
|
62 |
-import java.util.ArrayList; |
|
63 |
-import java.util.Arrays; |
|
64 |
-import java.util.Collections; |
|
65 |
-import java.util.Comparator; |
|
66 |
-import java.util.Date; |
|
67 |
-import java.util.HashMap; |
|
68 |
-import java.util.HashSet; |
|
69 |
-import java.util.List; |
|
70 |
-import java.util.Map; |
|
71 |
-import java.util.Set; |
|
72 |
-import java.io.IOException; |
|
73 |
-import java.io.OutputStream; |
|
74 | 43 |
import java.io.File; |
75 | 44 |
|
76 | 45 |
@Entity |
... | ... | @@ -610,7 +579,7 @@ |
610 | 579 |
* @return |
611 | 580 |
*/ |
612 | 581 |
@Transient |
613 |
- public List<CommitComment> getCodeComments() { |
|
582 |
+ public List<CommitComment> getCommitComments() { |
|
614 | 583 |
return CommitComment.findByCommits(fromProject, pullRequestCommits); |
615 | 584 |
} |
616 | 585 |
|
... | ... | @@ -625,15 +594,17 @@ |
625 | 594 |
|
626 | 595 |
/** |
627 | 596 |
* pull request의 모든 코멘트 정보를 가져오고 시간순으로 정렬 후 반환한다. (코멘트 + 코드코멘트 + 이벤트 ) |
597 |
+ * |
|
628 | 598 |
* @return |
629 | 599 |
*/ |
630 | 600 |
@Transient |
631 | 601 |
public List<TimelineItem> getTimelineComments() { |
632 |
- List<CommitComment> codeComments = getCodeComments(); |
|
633 |
- |
|
602 |
+ List<CommitComment> commitComment |
|
603 |
+ = computeCommitCommentReplies(getCommitComments()); |
|
604 |
+ |
|
634 | 605 |
List<TimelineItem> timelineComments = new ArrayList<>(); |
635 | 606 |
timelineComments.addAll(comments); |
636 |
- timelineComments.addAll(codeComments); |
|
607 |
+ timelineComments.addAll(commitComment); |
|
637 | 608 |
timelineComments.addAll(pullRequestEvents); |
638 | 609 |
|
639 | 610 |
Collections.sort(timelineComments, new Comparator<TimelineItem>() { |
... | ... | @@ -641,12 +612,97 @@ |
641 | 612 |
public int compare(TimelineItem o1, TimelineItem o2) { |
642 | 613 |
return o1.getDate().compareTo(o2.getDate()); |
643 | 614 |
} |
644 |
- |
|
615 |
+ |
|
645 | 616 |
}); |
646 |
- |
|
617 |
+ |
|
647 | 618 |
return timelineComments; |
648 | 619 |
} |
649 | 620 |
|
621 |
+ /** |
|
622 |
+ * 전체 코멘트중 부모글과 답글 정보를 재할당한다. |
|
623 |
+ * @param commitComments |
|
624 |
+ * @return |
|
625 |
+ */ |
|
626 |
+ private List<CommitComment> computeCommitCommentReplies( |
|
627 |
+ List<CommitComment> commitComments) { |
|
628 |
+ return reAssignReplyComments(sameTopicCommentGroups(commitComments)); |
|
629 |
+ } |
|
630 |
+ |
|
631 |
+ /** |
|
632 |
+ * 답글목록을 부모글의 필드로 재할당한다. |
|
633 |
+ * |
|
634 |
+ * commentGroup은 등록일순으로 오름차순 정렬되어 있는 상태이며 |
|
635 |
+ * 목록의 첫번째 코멘트를 부모글로 판단한다. |
|
636 |
+ * |
|
637 |
+ * @param commentGroup |
|
638 |
+ * @return |
|
639 |
+ */ |
|
640 |
+ private List<CommitComment> reAssignReplyComments( |
|
641 |
+ Map<String, List<CommitComment>> commentGroup) { |
|
642 |
+ List<CommitComment> parentCommitComments = new ArrayList<>(); |
|
643 |
+ |
|
644 |
+ for (List<CommitComment> commitComments : commentGroup.values()) { |
|
645 |
+ CommitComment parentComment = commitComments.get(0); |
|
646 |
+ if (hasReply(commitComments)) { |
|
647 |
+ parentComment.replies = replies(commitComments); |
|
648 |
+ } |
|
649 |
+ parentCommitComments.add(parentComment); |
|
650 |
+ } |
|
651 |
+ return parentCommitComments; |
|
652 |
+ } |
|
653 |
+ |
|
654 |
+ /** |
|
655 |
+ * 답글 목록을 반환한다. |
|
656 |
+ * @param commitComments |
|
657 |
+ * @return |
|
658 |
+ */ |
|
659 |
+ private List<CommitComment> replies(List<CommitComment> commitComments) { |
|
660 |
+ return commitComments.subList(1, commitComments.size()); |
|
661 |
+ } |
|
662 |
+ |
|
663 |
+ /** |
|
664 |
+ * 답글 유무를 체크한다. |
|
665 |
+ * @param commitComments |
|
666 |
+ * @return |
|
667 |
+ */ |
|
668 |
+ private boolean hasReply(List<CommitComment> commitComments) { |
|
669 |
+ return commitComments.size() > 1; |
|
670 |
+ } |
|
671 |
+ |
|
672 |
+ /** |
|
673 |
+ * groupKey를 통해 같은 코멘트그룹 목록을 반환한다. |
|
674 |
+ * (같은 커밋, 같은 파일, 같은 라인의 덧글들) |
|
675 |
+ * @param commitComments |
|
676 |
+ * @return |
|
677 |
+ */ |
|
678 |
+ private Map<String, List<CommitComment>> sameTopicCommentGroups( |
|
679 |
+ List<CommitComment> commitComments) { |
|
680 |
+ Map<String, List<CommitComment>> commentGroup = new HashMap<>(); |
|
681 |
+ for (CommitComment commitComment : commitComments) { |
|
682 |
+ commentGroup.put( |
|
683 |
+ commitComment.groupKey(), |
|
684 |
+ commitCommentsGroupByKey(commitComment.groupKey(), |
|
685 |
+ commitComments)); |
|
686 |
+ } |
|
687 |
+ return commentGroup; |
|
688 |
+ } |
|
689 |
+ |
|
690 |
+ /** |
|
691 |
+ * groupKey를 통해 같은 코멘트그룹을 반환한다. |
|
692 |
+ * @param groupKey |
|
693 |
+ * @param codeComments |
|
694 |
+ * @return |
|
695 |
+ */ |
|
696 |
+ private List<CommitComment> commitCommentsGroupByKey(String groupKey, |
|
697 |
+ List<CommitComment> codeComments) { |
|
698 |
+ List<CommitComment> commitCommentGroups = new ArrayList<CommitComment>(); |
|
699 |
+ for (CommitComment commitComment : codeComments) { |
|
700 |
+ if (commitComment.groupKey().equals(groupKey)) { |
|
701 |
+ commitCommentGroups.add(commitComment); |
|
702 |
+ } |
|
703 |
+ } |
|
704 |
+ return commitCommentGroups; |
|
705 |
+ } |
|
650 | 706 |
|
651 | 707 |
/** |
652 | 708 |
* 보낸 코드를 병합해보고 결과 정보를 반환한다. |
--- app/views/git/partial_comments.scala.html
+++ app/views/git/partial_comments.scala.html
... | ... | @@ -122,7 +122,7 @@ |
122 | 122 |
@renderCommitComment(comment: CommitComment) = { |
123 | 123 |
@defining(PullRequestCommit.getStateByCommitId(pull, comment.commitId)) { state => |
124 | 124 |
@if(state != null) { |
125 |
- <li class="comment @isAuthorComment(comment.authorLoginId)" id="comment-@comment.id"> |
|
125 |
+ <li class="comment @isAuthorComment(comment.authorLoginId) @if(!comment.replies.isEmpty) {hasReply}" id="comment-@comment.id"> |
|
126 | 126 |
<div class="comment-avatar"> |
127 | 127 |
<a href="@routes.UserApp.userInfo(comment.authorLoginId)" class="avatar-wrap" data-toggle="tooltip" data-placement="top" title="@comment.authorName"> |
128 | 128 |
<img src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="32" height="32" alt="@comment.authorLoginId"> |
... | ... | @@ -133,7 +133,7 @@ |
133 | 133 |
<span class="comment_author pull-left"> |
134 | 134 |
<i class="yobicon-comment"></i> |
135 | 135 |
<a href="@routes.UserApp.userInfo(comment.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@comment.authorName"> |
136 |
- <strong>@comment.authorLoginId </strong> |
|
136 |
+ <strong>@comment.authorLoginId</strong> |
|
137 | 137 |
</a> |
138 | 138 |
</span> |
139 | 139 |
<span class="ago"> |
... | ... | @@ -155,6 +155,36 @@ |
155 | 155 |
<div class="attachments pull-right" data-resourceType="@ResourceType.COMMIT_COMMENT" data-resourceId="@comment.id"></div> |
156 | 156 |
</div> |
157 | 157 |
</li> |
158 |
+ @for((reply, index) <- comment.replies.zipWithIndex) { |
|
159 |
+ <li class="comment @isAuthorComment(reply.authorLoginId) reply @if(index == (comment.replies.size - 1)) {last}" id="comment-@reply.id"> |
|
160 |
+ <div class="media-body"> |
|
161 |
+ <div class="meta-info"> |
|
162 |
+ <span class="comment_author pull-left"> |
|
163 |
+ <i class="yobicon-comment"></i> |
|
164 |
+ <a href="@routes.UserApp.userInfo(reply.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@reply.authorName"> |
|
165 |
+ <strong>@reply.authorLoginId</strong> |
|
166 |
+ </a> |
|
167 |
+ </span> |
|
168 |
+ <span class="ago"> |
|
169 |
+ @if(PullRequestCommit.State.PRIOR == state) { |
|
170 |
+ @utils.TemplateHelper.agoString(ago(reply.createdDate)) - <strike>@Messages("pullRequest.code.replied")</strike> |
|
171 |
+ } else { |
|
172 |
+ @utils.TemplateHelper.agoString(ago(reply.createdDate)) - @Messages("pullRequest.code.replied") |
|
173 |
+ } |
|
174 |
+ </span> |
|
175 |
+ @if(isAllowed(UserApp.currentUser(), reply.asResource(), Operation.DELETE)){ |
|
176 |
+ <span class="edit pull-right"> |
|
177 |
+ <button type="button" class="btn-transparent close" data-request-method="delete" data-request-uri="@routes.CodeHistoryApp.deleteComment(pull.fromProject.owner, pull.fromProject.name, reply.commitId, reply.id)"><i class="yobicon-trash"></i></button> |
|
178 |
+ </span> |
|
179 |
+ } |
|
180 |
+ </div> |
|
181 |
+ |
|
182 |
+ <div class="comment-body markdown-wrap markdown-before" markdown="true">@reply.contents</div> |
|
183 |
+ |
|
184 |
+ <div class="attachments pull-right" data-resourceType="@ResourceType.COMMIT_COMMENT" data-resourceId="@reply.id"></div> |
|
185 |
+ </div> |
|
186 |
+ </li> |
|
187 |
+ } |
|
158 | 188 |
} |
159 | 189 |
} |
160 | 190 |
} |
--- app/views/git/view.scala.html
+++ app/views/git/view.scala.html
... | ... | @@ -95,7 +95,7 @@ |
95 | 95 |
</div> |
96 | 96 |
} |
97 | 97 |
<div class="board-comment-wrap"> |
98 |
- <div class="comment-header"><i class="yobicon-comments"></i><strong>@Messages("common.comment")</strong> <strong class="num">@{pull.comments.size() + pull.getCodeComments.size()}</strong></div> |
|
98 |
+ <div class="comment-header"><i class="yobicon-comments"></i><strong>@Messages("common.comment")</strong> <strong class="num">@{pull.comments.size() + pull.getCommitComments.size()}</strong></div> |
|
99 | 99 |
|
100 | 100 |
@if(pull.getTimelineComments.size > 0) { |
101 | 101 |
<ul class="comments" id="comments"> |
--- conf/messages
+++ conf/messages
... | ... | @@ -437,6 +437,7 @@ |
437 | 437 |
pullRequest.body.required = Explain this pull request. |
438 | 438 |
pullRequest.cancel = Cancel |
439 | 439 |
pullRequest.code.commented = commented on {0} |
440 |
+pullRequest.code.replied = replied |
|
440 | 441 |
pullRequest.conflict.files = Conflicted Files |
441 | 442 |
pullRequest.delete.branch = Delete Branch |
442 | 443 |
pullRequest.delete.frombranch.message = can be deleted. |
--- conf/messages.ja
+++ conf/messages.ja
... | ... | @@ -438,6 +438,7 @@ |
438 | 438 |
pullRequest.body.required = 説明を入力してください |
439 | 439 |
pullRequest.cancel = 取り消し |
440 | 440 |
pullRequest.code.commented = commented on {0} |
441 |
+pullRequest.code.replied = replied |
|
441 | 442 |
pullRequest.conflict.files = コンフリクトが発生したファイル |
442 | 443 |
pullRequest.delete.branch = ブランチ削除 |
443 | 444 |
pullRequest.delete.frombranch.message = ブランチを削除できます |
--- conf/messages.ko
+++ conf/messages.ko
... | ... | @@ -438,6 +438,7 @@ |
438 | 438 |
pullRequest.body.required = 내용을 입력하세요. |
439 | 439 |
pullRequest.cancel = 취소 |
440 | 440 |
pullRequest.code.commented = {0} 에 덧글을 작성했습니다. |
441 |
+pullRequest.code.replied = 답글을 작성했습니다. |
|
441 | 442 |
pullRequest.conflict.files = 충돌난 파일 |
442 | 443 |
pullRequest.delete.branch = 브랜치 삭제 |
443 | 444 |
pullRequest.delete.frombranch.message = 브랜치를 삭제할 수 있습니다. |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?