Yi EungJun 2012-12-05
issue: Introduce Assignee model.
Assignee is a user assigned to any issue in a particular project.

Advanced Search feature gets all assignees in a particular project
using this model and show them in assignee field in the search form.
@edd56749a0a162f7d394baee60a6fac9ce6d0a3c
app/controllers/IssueApp.java
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
@@ -106,10 +106,15 @@
             newIssue.authorName = UserApp.currentUser().name;
             newIssue.project = project;
             newIssue.state = State.OPEN;
-
-            String[] labelIds = request().body().asMultipartFormData().asFormUrlEncoded().get("labelIds");
+            if (newIssue.assignee.id != null) {
+                newIssue.assignee.project = project;
+            } else {
+                newIssue.assignee = null;
+            }
+            String[] labelIds = request().body().asMultipartFormData().asFormUrlEncoded()
+                    .get("labelIds");
             if (labelIds != null) {
-                for (String labelId: labelIds) {
+                for (String labelId : labelIds) {
                     newIssue.labels.add(IssueLabel.findById(Long.parseLong(labelId)));
                 }
             }
@@ -150,6 +155,11 @@
         issue.authorName = originalIssue.authorName;
         issue.project = originalIssue.project;
         String[] labelIds = request().body().asMultipartFormData().asFormUrlEncoded().get("labelIds");
+        if (issue.assignee.id != null) {
+            issue.assignee.project = originalIssue.project;
+        } else {
+            issue.assignee = null;
+        }
         if (labelIds != null) {
             for (String labelId: labelIds) {
                 issue.labels.add(IssueLabel.findById(Long.parseLong(labelId)));
@@ -158,6 +168,10 @@
 
         Issue.edit(issue);
 
+        if (originalIssue.assignee != null) {
+            originalIssue.assignee.deleteIfEmpty();
+        }
+
         // Attach the files in the current user's temporary storage.
         Attachment.attachFiles(UserApp.currentUser().id, originalIssue.project.id, Resource.ISSUE_POST, id);
 
 
app/models/Assignee.java (added)
+++ app/models/Assignee.java
@@ -0,0 +1,82 @@
+package models;
+
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import play.Logger;
+import play.data.validation.Constraints.Required;
+import play.db.ebean.Model;
+import play.db.ebean.Model.Finder;
+
+@Entity
+public class Assignee extends Model {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    @Id
+    public Long id;
+
+    @ManyToOne
+    @Required
+    public User user;
+
+    @ManyToOne
+    @Required
+    public Project project;
+
+    @OneToMany(mappedBy = "assignee", cascade = CascadeType.ALL)
+    public Set<Issue> issues;
+
+    public static Finder<Long, Assignee> finder = new Finder<Long, Assignee>(Long.class,
+            Assignee.class);
+
+    public Assignee(Long userId, Long projectId) {
+        user = User.find.byId(userId);
+        project = Project.find.byId(projectId);
+    }
+
+    static public Assignee get(Long userId, Long projectId) {
+        Assignee assignee = finder.where().eq("user.id", userId).eq("project.id", projectId)
+                .findUnique();
+        if (assignee == null) {
+            assignee = new Assignee(userId, projectId);
+        }
+        return assignee;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+        if (!(that instanceof Assignee))
+            return false;
+        Assignee castedThat = (Assignee) that;
+        if (id == castedThat.id)
+            return true;
+        return (this == castedThat) || (user.id == castedThat.user.id)
+                && (project.id == castedThat.project.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().append(user.hashCode()).append(project.hashCode())
+                .toHashCode();
+    }
+
+    public void deleteIfEmpty() {
+        Logger.debug("empty?");
+        Logger.debug(String.valueOf(issues.size()));
+        if (this.issues.isEmpty()) {
+            Logger.debug("del");
+            super.delete();
+        }
+    }
+}(No newline at end of file)
app/models/Issue.java
--- app/models/Issue.java
+++ app/models/Issue.java
@@ -65,7 +65,6 @@
 
     public int numOfComments;
     public Long milestoneId;
-    public Long assigneeId;
     public Long authorId;
     public String authorName;
     public State state;
@@ -80,6 +79,9 @@
 
     @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
     public Set<IssueLabel> labels;
+
+    @ManyToOne(cascade = CascadeType.ALL)
+    public Assignee assignee;
 
     public Issue(String title) {
         this.title = title;
@@ -106,7 +108,7 @@
      * 있을듯.
      */
     public String assigneeName() {
-        return (this.assigneeId != null ? User.findNameById(this.assigneeId) : null);
+        return (this.assignee != null ? assignee.user.name : null);
     }
 
     /**
@@ -223,7 +225,12 @@
             Milestone milestone = Milestone.findById(issue.milestoneId);
             milestone.delete(issue);
         }
+        Assignee a = issue.assignee;
         issue.delete();
+        if (a != null) {
+            a.refresh();
+            a.deleteIfEmpty();
+        }
     }
 
     /**
@@ -381,7 +388,7 @@
     }
 
     public static Long findAssigneeIdByIssueId(Long issueId) {
-        return finder.byId(issueId).assigneeId;
+        return finder.byId(issueId).assignee.user.id;
     }
 
     /**
@@ -439,7 +446,7 @@
                 sheet.addCell(new Label(colcnt++, i, issue.id.toString(), cf2));
                 sheet.addCell(new Label(colcnt++, i, issue.state.toString(), cf2));
                 sheet.addCell(new Label(colcnt++, i, issue.title, cf2));
-                sheet.addCell(new Label(colcnt++, i, getAssigneeName(issue.assigneeId), cf2));
+                sheet.addCell(new Label(colcnt++, i, getAssigneeName(issue.assignee), cf2));
                 sheet.addCell(new Label(colcnt++, i, issue.date.toString(), cf2));
             }
             workbook.write();
@@ -465,8 +472,8 @@
      * @param uId
      * @return
      */
-    private static String getAssigneeName(Long uId) {
-        return (uId != null ? User.findNameById(uId) : TO_BE_ASSIGNED);
+    private static String getAssigneeName(Assignee assignee) {
+        return (assignee != null ? assignee.user.name : TO_BE_ASSIGNED);
     }
 
     // FIXME 이것이 없이 테스트는 잘 작동하나, view에서 댓글이 달린 이슈들을 필터링하는 라디오버튼을 작동시에 이 메쏘드에서
app/models/support/SearchCondition.java
--- app/models/support/SearchCondition.java
+++ app/models/support/SearchCondition.java
@@ -20,6 +20,8 @@
     public Boolean fileAttachedCheck;
     public Long milestone;
     public Set<Long> labelIds;
+    public String authorLoginId;
+    public Long assigneeId;
 
     public SearchCondition() {
         filter = "";
app/views/issue/editIssue.scala.html
--- app/views/issue/editIssue.scala.html
+++ app/views/issue/editIssue.scala.html
@@ -55,7 +55,7 @@
             }
       @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) {
             @select(
-                issueForm("assigneeId"),
+                issueForm("assignee.user.id"),
                 options(ProjectUser.options(project.id)),
                 '_label-> Messages("issue.new.detailInfo.assignee"),
                 '_default -> Messages("issue.new.selectDefault.assignee"),
app/views/issue/newIssue.scala.html
--- app/views/issue/newIssue.scala.html
+++ app/views/issue/newIssue.scala.html
@@ -46,7 +46,7 @@
         <fieldset class="labels">
           @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) {
                 @select(
-                    issueForm("assigneeId"),
+                    issueForm("assignee.user.id"),
                     options(ProjectUser.options(project.id)),
                     '_label-> Messages("issue.new.detailInfo.assignee"),
                     '_default -> Messages("issue.new.selectDefault.assignee"),
app/views/search/issueContentsSearch.scala.html
--- app/views/search/issueContentsSearch.scala.html
+++ app/views/search/issueContentsSearch.scala.html
@@ -8,7 +8,7 @@
       <td><a href="@routes.IssueApp.issue(project.owner, project.name, issue.id)">
               @issue.title @if(issue.comments.size > 0){[@issue.comments.size]}</a></td>
       <td>
-        @if(issue.assigneeId == null){
+        @if(issue.assignee == null){
         <em>@Messages("issue.noAssignee")</em>
         } else {
         <em>@issue.assigneeName</em>
conf/initial-data.yml
--- conf/initial-data.yml
+++ conf/initial-data.yml
@@ -90,8 +90,6 @@
         authorName:     hobi
         post:           !!models.Post
                             id: 1
-
-        
 # Issues
 issues:
     - !!models.Issue
@@ -105,7 +103,11 @@
                             id: 1
     - !!models.Issue
         authorId:     3
-        assigneeId:     2     
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 2
+                     project: !!models.Project
+                                id: 1
         title:          다운로드는 익명 댓글에도 사용자명에 링크가 걸림
         body:           내용 다운로드는 익명 댓글에도 사용자명에 링크가 걸림
         state:      OPEN
@@ -115,7 +117,11 @@
                             id: 1
     - !!models.Issue
         authorId:     4
-        assigneeId:     2
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 2
+                     project: !!models.Project
+                                id: 1
         title:          gittracker.php의 메모리 제한 에러
         body:           내용 gittracker.php의 메모리 제한 에러
         state:      CLOSED
@@ -126,7 +132,11 @@
         numOfComments:  1
     - !!models.Issue
         authorId:     4
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 1
         title:          git/hg 코드 브라우저에 i18n이 적용되지 않음
         body:           내용 git/hg 코드 브라우저에 i18n이 적용되지 않음
         state:      CLOSED
@@ -136,7 +146,11 @@
                             id: 1
     - !!models.Issue
         authorId:     4
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 1
         title:          CUBRID 설치 문제
         body:           IOS는 설치 못하나요?
         state:      OPEN
@@ -146,7 +160,11 @@
                             id: 3
     - !!models.Issue
         authorId:     4
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 1
         title:          메모리 누수 현상
         body:           메모리가 너무 누수가 되는듯.
         state:      CLOSED
@@ -156,7 +174,11 @@
                             id: 3
     - !!models.Issue
         authorId:     4
-        assigneeId:    3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 1
         title:          Client application for Mac.
         body:           Please make it.
         state:      OPEN
@@ -166,7 +188,11 @@
                             id: 3
     - !!models.Issue
         authorId:     4
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 1
         title:          CPU 무한 점유울 문제.
         body:           CPU를 무한사용 중이에요.
         state:      CLOSED
@@ -176,7 +202,11 @@
                             id: 3
     - !!models.Issue
         authorId:     1
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 1
         title:          Less chained imports causes compile error
         body:           When using Play, when I chain less files such as a.less imports b.less which in turn imports c.less
         state:      CLOSED
@@ -186,7 +216,11 @@
                             id: 1
     - !!models.Issue
         authorId:     1
-        assigneeId:     2
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 2
+                     project: !!models.Project
+                                id: 1
         title:          Weird TypeDoesNotMatch exception in RC-3 and final
         body:           The following code works as expected in RC1-Snapshot, but breaks with an TypeDoesNotMatch exception in RC-3 and 2.0 final.
         state:      CLOSED
@@ -205,7 +239,11 @@
                             id: 1
     - !!models.Issue
         authorId:     1
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 2
         title:          Update sbt-idea to 1.1.0
         body:           Create sbt project definition module, if exists, for each subproject (pull 128)
         state:      CLOSED
@@ -215,7 +253,11 @@
                             id: 2
     - !!models.Issue
         authorId:     1
-        assigneeId:    3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 2
         title:          Support Tuple 22, not just Tuple 18 in api/data/Forms.scala
         body:           While creating some complex forms, Eclipse started hanging for some unknown reason.
         state:      CLOSED
@@ -225,7 +267,11 @@
                             id: 2
     - !!models.Issue
         authorId:     1
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 2
         title:          Support Tuple 22, not just Tuple 18 in api/data/Forms.scala
         body:           While creating some complex forms, Eclipse started hanging for some unknown reason.
         state:      OPEN
@@ -235,7 +281,11 @@
                             id: 2
     - !!models.Issue
         authorId:     1
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 2
         title:          Cookie Expires date
         body:           With Play 2.0.2, when creating a cookie with response().setCookie, HTTP output is like
         state:      OPEN
@@ -245,7 +295,11 @@
                             id: 2
     - !!models.Issue
         authorId:     1
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 2
         title:          require is not work in windows (Closure Compiler)
         body:           Test on Play2.1-Snapshot
         state:      OPEN
@@ -255,7 +309,11 @@
                             id: 2
     - !!models.Issue
         authorId:     2
-        assigneeId:     3
+        assignee: !!models.Assignee
+                     user: !!models.User
+                                id: 3
+                     project: !!models.Project
+                                id: 3
         title:          Remember me is not working
         body:           Test on Play2.1-Snapshot
         state:      OPEN
Add a comment
List