doortts doortts 2014-02-10
comment-and-close: now user can close issue with comment
 known-issue: mail send twice. One is comment and another is close
 event.
@e8a4d5d5098ccb906ef1106b57227721e5bb2857
app/controllers/IssueApp.java
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
@@ -16,6 +16,7 @@
 import org.apache.tika.Tika;
 import org.codehaus.jackson.node.ObjectNode;
 import play.data.Form;
+import play.data.validation.ValidationError;
 import play.db.ebean.Transactional;
 import play.i18n.Messages;
 import play.libs.Json;
@@ -721,6 +722,76 @@
     }
 
     /**
+     * 댓글 작성과 상태 변경을 함께 처리
+     *
+     * <p>when: 이슈 조회화면에서 댓글 작성하고 저장하면서 상태까지 변경할 때</p>
+     *
+     * 현재 사용자를 댓글 작성자로 하여 저장하고 이슈 조회화면으로 돌아간다.
+     *
+     * @param ownerName 프로젝트 소유자 이름
+     * @param projectName 프로젝트 이름
+     * @param number 이슈 번호
+     * @return
+     * @throws IOException
+     * @see {@link AbstractPostingApp#newComment(Comment, Form, Call, Runnable)}
+     */
+    @Transactional
+    @IsCreatable(ResourceType.ISSUE_COMMENT)
+    public static Result newCommentWithState(String ownerName, String projectName, Long number) throws IOException {
+        Project project = Project.findByOwnerAndProjectName(ownerName, projectName);
+        Form<IssueComment> commentForm = new Form<>(IssueComment.class).bindFromRequest();
+        if (commentForm.hasErrors()) {
+            return commentFormValidationResult(project, commentForm);
+        }
+
+        toNextState(number, project);
+
+        final IssueComment comment = commentForm.get();
+        final Issue issue = Issue.findByNumber(project, number);
+
+        commentSave(comment, issue);
+
+        // Attach all of the files in the current user's temporary storage.
+        Attachment.moveAll(UserApp.currentUser().asResource(), comment.asResource());
+
+        String urlToView = routes.IssueApp.issue(project.owner, project.name, number) + "#comment-" + comment.id;
+        NotificationEvent.afterNewComment(comment, urlToView);
+        IssueEvent.addFromNotificationEvent(
+                NotificationEvent.afterStateChanged(issue.previousState(), issue, urlToView),
+                issue, UserApp.currentUser().loginId);
+        return redirect(urlToView);
+    }
+
+    private static void commentSave(final IssueComment comment, final Issue issue) {
+        comment.setAuthor(UserApp.currentUser());
+        new Runnable() {
+            @Override
+            public void run() {
+                comment.issue = issue;
+            }
+        }.run();
+        comment.save();
+    }
+
+    private static void toNextState(Long number, Project project) {
+        String withStateTransition = request().body().asMultipartFormData().asFormUrlEncoded().get("withStateTransition")[0];
+        final Issue issue = Issue.findByNumber(project, number);
+        if(StringUtils.isNotBlank(withStateTransition)) {
+            issue.toNextState();
+        }
+    }
+
+    private static Result commentFormValidationResult(Project project, Form<IssueComment> commentForm) {
+        Map<String,List<ValidationError>> errors = commentForm.errors();
+        if( errors.get("contents") != null ){
+            return badRequest(ErrorViews.BadRequest.render("post.comment.empty", project));
+        } else {
+            return badRequest(ErrorViews.BadRequest.render("error.validation", project));
+        }
+    }
+
+
+    /**
      * 댓글 삭제
      *
      * <p>when: 댓글 삭제 버튼</p>
app/models/NotificationEvent.java
--- app/models/NotificationEvent.java
+++ app/models/NotificationEvent.java
@@ -367,6 +367,29 @@
     }
 
     /**
+     * 이슈와 게시물에 새 댓글을 달렸을 때 알림을 추가한다.
+     *
+     * @param comment
+     * @param urlToView
+     */
+    public static void afterNewCommentWithState(Comment comment, String urlToView, State state) {
+        AbstractPosting post = comment.getParent();
+
+        NotificationEvent notiEvent = createFromCurrentUser(comment);
+        notiEvent.title = formatReplyTitle(post);
+        notiEvent.urlToView = urlToView;
+        Set<User> receivers = getReceivers(post);
+        receivers.addAll(getMentionedUsers(comment.contents));
+        receivers.remove(UserApp.currentUser());
+        notiEvent.receivers = receivers;
+        notiEvent.eventType = EventType.NEW_COMMENT;
+        notiEvent.oldValue = null;
+        notiEvent.newValue = comment.contents + "\n" + state.state();
+
+        NotificationEvent.add(notiEvent);
+    }
+
+    /**
      * 상태 변경에 대한 알림을 추가한다.
      *
      * 등록된 notification은 사이트 메인 페이지를 통해 사용자에게 보여지며 또한
app/views/common/commentForm.scala.html
--- app/views/common/commentForm.scala.html
+++ app/views/common/commentForm.scala.html
@@ -24,6 +24,7 @@
                 }
                 @** end of fileUploader **@
                 <div class="right-txt">
+                    <button type="button" class="ybtn hidden" id="dynamic-comment-btn"></button>
                     <button type="submit" class="ybtn ybtn-success">@Messages("button.comment.new")</button>
                 </div>
             </div>
app/views/issue/view.scala.html
--- app/views/issue/view.scala.html
+++ app/views/issue/view.scala.html
@@ -306,4 +306,38 @@
         });
     });
 </script>
+
+<script>
+    //add "comment & close" like button at comment form
+    $(function(){
+        var welEditor = $("#comment-editor");
+        var welDynamicCommentBtn = $("#dynamic-comment-btn");
+        var welCommentForm = $("#comment-form" );
+        var welWithStateTransition = $("<input type='hidden' name='withStateTransition'>");
+
+        var sNextState = "@Messages("button.nextState."+issue.nextState().toString.toLowerCase)";
+        var sCommentAndNextState = "@Html(Messages("button.commentAndNextState."+issue.nextState().toString.toLowerCase))";
+        welCommentForm.prepend(welWithStateTransition);
+        welDynamicCommentBtn.removeClass('hidden');
+        welDynamicCommentBtn.html("@Messages("button.nextState."+issue.nextState().toString.toLowerCase)")
+            .on("click", function(){
+                if(welEditor.val().length > 0){
+                    welWithStateTransition.val("true");
+                    welCommentForm.attr("action", "@routes.IssueApp.newCommentWithState(project.owner, project.name, issue.getNumber)");
+                    welCommentForm.submit();
+                } else {
+                    welWithStateTransition.val("");
+                    location.href="@routes.IssueApp.nextState(project.owner, project.name, issue.getNumber)";
+                }
+            });
+        welEditor.on("keyup", function(){
+            if(welEditor.val().length > 0){
+                welDynamicCommentBtn.html(sCommentAndNextState);
+                welDynamicCommentBtn.css()
+            } else {
+                welDynamicCommentBtn.html(sNextState);
+            }
+        });
+    });
+</script>
 }
conf/messages
--- conf/messages
+++ conf/messages
@@ -37,6 +37,8 @@
 button.back = Back
 button.cancel = Cancel
 button.cancel.enrollment = Cancel enrollment.
+button.commentAndNextState.closed = Comment & Close
+button.commentAndNextState.open = Comment & Reopen
 button.comment.new = Add Comment
 button.confirm = Confirm
 button.delete = Delete
@@ -45,6 +47,8 @@
 button.download = Download
 button.edit = Edit
 button.excelDownload = Excel
+button.nextState.close = Close
+button.nextState.open = Reopen
 button.list = list
 button.login = Login
 button.new.enrollment = New enrollment.
conf/messages.ko
--- conf/messages.ko
+++ conf/messages.ko
@@ -37,6 +37,8 @@
 button.back = 돌아가기
 button.cancel = 취소
 button.cancel.enrollment = 멤버 등록 요청 취소하기
+button.commentAndNextState.closed = 댓글 입력하고 이슈 닫기
+button.commentAndNextState.open = 댓글 입력하고 이슈 다시 열기
 button.comment.new = 댓글 입력
 button.confirm = 확인
 button.delete = 삭제
@@ -45,6 +47,8 @@
 button.download = 파일 받기
 button.edit = 수정
 button.excelDownload = Excel
+button.nextState.closed = 이슈 닫기
+button.nextState.open = 이슈 다시 열기
 button.list = 목록
 button.login = 로그인
 button.new.enrollment = 멤버 등록 요청하기
conf/routes
--- conf/routes
+++ conf/routes
@@ -138,6 +138,7 @@
 GET            /:user/:project/issue/$number<[0-9]+>/nextstate                        controllers.IssueApp.nextState(user, project, number:Long)
 DELETE         /:user/:project/issue/$number<[0-9]+>/delete                           controllers.IssueApp.deleteIssue(user, project, number:Long)
 POST           /:user/:project/issue/$number<[0-9]+>/comments                         controllers.IssueApp.newComment(user, project, number:Long)
+POST           /:user/:project/issue/$number<[0-9]+>/commentsWithState                controllers.IssueApp.newCommentWithState(user, project, number:Long)
 DELETE         /:user/:project/issue/$number<[0-9]+>/comment/:commentId/delete        controllers.IssueApp.deleteComment(user, project, number:Long, commentId:Long)
 GET            /:user/:project/issue/$number<[0-9]+>/timeline                         controllers.IssueApp.timeline(user, project, number:Long)
 
Add a comment
List