[Notice] Announcing the End of Demo Server [Read me]

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
... | ... | @@ -106,10 +106,15 @@ |
106 | 106 |
newIssue.authorName = UserApp.currentUser().name; |
107 | 107 |
newIssue.project = project; |
108 | 108 |
newIssue.state = State.OPEN; |
109 |
- |
|
110 |
- String[] labelIds = request().body().asMultipartFormData().asFormUrlEncoded().get("labelIds"); |
|
109 |
+ if (newIssue.assignee.id != null) { |
|
110 |
+ newIssue.assignee.project = project; |
|
111 |
+ } else { |
|
112 |
+ newIssue.assignee = null; |
|
113 |
+ } |
|
114 |
+ String[] labelIds = request().body().asMultipartFormData().asFormUrlEncoded() |
|
115 |
+ .get("labelIds"); |
|
111 | 116 |
if (labelIds != null) { |
112 |
- for (String labelId: labelIds) { |
|
117 |
+ for (String labelId : labelIds) { |
|
113 | 118 |
newIssue.labels.add(IssueLabel.findById(Long.parseLong(labelId))); |
114 | 119 |
} |
115 | 120 |
} |
... | ... | @@ -150,6 +155,11 @@ |
150 | 155 |
issue.authorName = originalIssue.authorName; |
151 | 156 |
issue.project = originalIssue.project; |
152 | 157 |
String[] labelIds = request().body().asMultipartFormData().asFormUrlEncoded().get("labelIds"); |
158 |
+ if (issue.assignee.id != null) { |
|
159 |
+ issue.assignee.project = originalIssue.project; |
|
160 |
+ } else { |
|
161 |
+ issue.assignee = null; |
|
162 |
+ } |
|
153 | 163 |
if (labelIds != null) { |
154 | 164 |
for (String labelId: labelIds) { |
155 | 165 |
issue.labels.add(IssueLabel.findById(Long.parseLong(labelId))); |
... | ... | @@ -158,6 +168,10 @@ |
158 | 168 |
|
159 | 169 |
Issue.edit(issue); |
160 | 170 |
|
171 |
+ if (originalIssue.assignee != null) { |
|
172 |
+ originalIssue.assignee.deleteIfEmpty(); |
|
173 |
+ } |
|
174 |
+ |
|
161 | 175 |
// Attach the files in the current user's temporary storage. |
162 | 176 |
Attachment.attachFiles(UserApp.currentUser().id, originalIssue.project.id, Resource.ISSUE_POST, id); |
163 | 177 |
|
+++ app/models/Assignee.java
... | ... | @@ -0,0 +1,82 @@ |
1 | +package models; | |
2 | + | |
3 | +import java.util.Set; | |
4 | + | |
5 | +import javax.persistence.CascadeType; | |
6 | +import javax.persistence.Entity; | |
7 | +import javax.persistence.Id; | |
8 | +import javax.persistence.ManyToOne; | |
9 | +import javax.persistence.OneToMany; | |
10 | + | |
11 | +import org.apache.commons.lang.builder.HashCodeBuilder; | |
12 | + | |
13 | +import play.Logger; | |
14 | +import play.data.validation.Constraints.Required; | |
15 | +import play.db.ebean.Model; | |
16 | +import play.db.ebean.Model.Finder; | |
17 | + | |
18 | +@Entity | |
19 | +public class Assignee extends Model { | |
20 | + | |
21 | + /** | |
22 | + * | |
23 | + */ | |
24 | + private static final long serialVersionUID = 1L; | |
25 | + | |
26 | + @Id | |
27 | + public Long id; | |
28 | + | |
29 | + @ManyToOne | |
30 | + @Required | |
31 | + public User user; | |
32 | + | |
33 | + @ManyToOne | |
34 | + @Required | |
35 | + public Project project; | |
36 | + | |
37 | + @OneToMany(mappedBy = "assignee", cascade = CascadeType.ALL) | |
38 | + public Set<Issue> issues; | |
39 | + | |
40 | + public static Finder<Long, Assignee> finder = new Finder<Long, Assignee>(Long.class, | |
41 | + Assignee.class); | |
42 | + | |
43 | + public Assignee(Long userId, Long projectId) { | |
44 | + user = User.find.byId(userId); | |
45 | + project = Project.find.byId(projectId); | |
46 | + } | |
47 | + | |
48 | + static public Assignee get(Long userId, Long projectId) { | |
49 | + Assignee assignee = finder.where().eq("user.id", userId).eq("project.id", projectId) | |
50 | + .findUnique(); | |
51 | + if (assignee == null) { | |
52 | + assignee = new Assignee(userId, projectId); | |
53 | + } | |
54 | + return assignee; | |
55 | + } | |
56 | + | |
57 | + @Override | |
58 | + public boolean equals(Object that) { | |
59 | + if (!(that instanceof Assignee)) | |
60 | + return false; | |
61 | + Assignee castedThat = (Assignee) that; | |
62 | + if (id == castedThat.id) | |
63 | + return true; | |
64 | + return (this == castedThat) || (user.id == castedThat.user.id) | |
65 | + && (project.id == castedThat.project.id); | |
66 | + } | |
67 | + | |
68 | + @Override | |
69 | + public int hashCode() { | |
70 | + return new HashCodeBuilder().append(user.hashCode()).append(project.hashCode()) | |
71 | + .toHashCode(); | |
72 | + } | |
73 | + | |
74 | + public void deleteIfEmpty() { | |
75 | + Logger.debug("empty?"); | |
76 | + Logger.debug(String.valueOf(issues.size())); | |
77 | + if (this.issues.isEmpty()) { | |
78 | + Logger.debug("del"); | |
79 | + super.delete(); | |
80 | + } | |
81 | + } | |
82 | +}(No newline at end of file) |
--- app/models/Issue.java
+++ app/models/Issue.java
... | ... | @@ -65,7 +65,6 @@ |
65 | 65 |
|
66 | 66 |
public int numOfComments; |
67 | 67 |
public Long milestoneId; |
68 |
- public Long assigneeId; |
|
69 | 68 |
public Long authorId; |
70 | 69 |
public String authorName; |
71 | 70 |
public State state; |
... | ... | @@ -80,6 +79,9 @@ |
80 | 79 |
|
81 | 80 |
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) |
82 | 81 |
public Set<IssueLabel> labels; |
82 |
+ |
|
83 |
+ @ManyToOne(cascade = CascadeType.ALL) |
|
84 |
+ public Assignee assignee; |
|
83 | 85 |
|
84 | 86 |
public Issue(String title) { |
85 | 87 |
this.title = title; |
... | ... | @@ -106,7 +108,7 @@ |
106 | 108 |
* 있을듯. |
107 | 109 |
*/ |
108 | 110 |
public String assigneeName() { |
109 |
- return (this.assigneeId != null ? User.findNameById(this.assigneeId) : null); |
|
111 |
+ return (this.assignee != null ? assignee.user.name : null); |
|
110 | 112 |
} |
111 | 113 |
|
112 | 114 |
/** |
... | ... | @@ -223,7 +225,12 @@ |
223 | 225 |
Milestone milestone = Milestone.findById(issue.milestoneId); |
224 | 226 |
milestone.delete(issue); |
225 | 227 |
} |
228 |
+ Assignee a = issue.assignee; |
|
226 | 229 |
issue.delete(); |
230 |
+ if (a != null) { |
|
231 |
+ a.refresh(); |
|
232 |
+ a.deleteIfEmpty(); |
|
233 |
+ } |
|
227 | 234 |
} |
228 | 235 |
|
229 | 236 |
/** |
... | ... | @@ -381,7 +388,7 @@ |
381 | 388 |
} |
382 | 389 |
|
383 | 390 |
public static Long findAssigneeIdByIssueId(Long issueId) { |
384 |
- return finder.byId(issueId).assigneeId; |
|
391 |
+ return finder.byId(issueId).assignee.user.id; |
|
385 | 392 |
} |
386 | 393 |
|
387 | 394 |
/** |
... | ... | @@ -439,7 +446,7 @@ |
439 | 446 |
sheet.addCell(new Label(colcnt++, i, issue.id.toString(), cf2)); |
440 | 447 |
sheet.addCell(new Label(colcnt++, i, issue.state.toString(), cf2)); |
441 | 448 |
sheet.addCell(new Label(colcnt++, i, issue.title, cf2)); |
442 |
- sheet.addCell(new Label(colcnt++, i, getAssigneeName(issue.assigneeId), cf2)); |
|
449 |
+ sheet.addCell(new Label(colcnt++, i, getAssigneeName(issue.assignee), cf2)); |
|
443 | 450 |
sheet.addCell(new Label(colcnt++, i, issue.date.toString(), cf2)); |
444 | 451 |
} |
445 | 452 |
workbook.write(); |
... | ... | @@ -465,8 +472,8 @@ |
465 | 472 |
* @param uId |
466 | 473 |
* @return |
467 | 474 |
*/ |
468 |
- private static String getAssigneeName(Long uId) { |
|
469 |
- return (uId != null ? User.findNameById(uId) : TO_BE_ASSIGNED); |
|
475 |
+ private static String getAssigneeName(Assignee assignee) { |
|
476 |
+ return (assignee != null ? assignee.user.name : TO_BE_ASSIGNED); |
|
470 | 477 |
} |
471 | 478 |
|
472 | 479 |
// FIXME 이것이 없이 테스트는 잘 작동하나, view에서 댓글이 달린 이슈들을 필터링하는 라디오버튼을 작동시에 이 메쏘드에서 |
--- app/models/support/SearchCondition.java
+++ app/models/support/SearchCondition.java
... | ... | @@ -20,6 +20,8 @@ |
20 | 20 |
public Boolean fileAttachedCheck; |
21 | 21 |
public Long milestone; |
22 | 22 |
public Set<Long> labelIds; |
23 |
+ public String authorLoginId; |
|
24 |
+ public Long assigneeId; |
|
23 | 25 |
|
24 | 26 |
public SearchCondition() { |
25 | 27 |
filter = ""; |
--- app/views/issue/editIssue.scala.html
+++ app/views/issue/editIssue.scala.html
... | ... | @@ -55,7 +55,7 @@ |
55 | 55 |
} |
56 | 56 |
@isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) { |
57 | 57 |
@select( |
58 |
- issueForm("assigneeId"), |
|
58 |
+ issueForm("assignee.user.id"), |
|
59 | 59 |
options(ProjectUser.options(project.id)), |
60 | 60 |
'_label-> Messages("issue.new.detailInfo.assignee"), |
61 | 61 |
'_default -> Messages("issue.new.selectDefault.assignee"), |
--- app/views/issue/newIssue.scala.html
+++ app/views/issue/newIssue.scala.html
... | ... | @@ -46,7 +46,7 @@ |
46 | 46 |
<fieldset class="labels"> |
47 | 47 |
@isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) { |
48 | 48 |
@select( |
49 |
- issueForm("assigneeId"), |
|
49 |
+ issueForm("assignee.user.id"), |
|
50 | 50 |
options(ProjectUser.options(project.id)), |
51 | 51 |
'_label-> Messages("issue.new.detailInfo.assignee"), |
52 | 52 |
'_default -> Messages("issue.new.selectDefault.assignee"), |
--- app/views/search/issueContentsSearch.scala.html
+++ app/views/search/issueContentsSearch.scala.html
... | ... | @@ -8,7 +8,7 @@ |
8 | 8 |
<td><a href="@routes.IssueApp.issue(project.owner, project.name, issue.id)"> |
9 | 9 |
@issue.title @if(issue.comments.size > 0){[@issue.comments.size]}</a></td> |
10 | 10 |
<td> |
11 |
- @if(issue.assigneeId == null){ |
|
11 |
+ @if(issue.assignee == null){ |
|
12 | 12 |
<em>@Messages("issue.noAssignee")</em> |
13 | 13 |
} else { |
14 | 14 |
<em>@issue.assigneeName</em> |
--- conf/initial-data.yml
+++ conf/initial-data.yml
... | ... | @@ -90,8 +90,6 @@ |
90 | 90 |
authorName: hobi |
91 | 91 |
post: !!models.Post |
92 | 92 |
id: 1 |
93 |
- |
|
94 |
- |
|
95 | 93 |
# Issues |
96 | 94 |
issues: |
97 | 95 |
- !!models.Issue |
... | ... | @@ -105,7 +103,11 @@ |
105 | 103 |
id: 1 |
106 | 104 |
- !!models.Issue |
107 | 105 |
authorId: 3 |
108 |
- assigneeId: 2 |
|
106 |
+ assignee: !!models.Assignee |
|
107 |
+ user: !!models.User |
|
108 |
+ id: 2 |
|
109 |
+ project: !!models.Project |
|
110 |
+ id: 1 |
|
109 | 111 |
title: 다운로드는 익명 댓글에도 사용자명에 링크가 걸림 |
110 | 112 |
body: 내용 다운로드는 익명 댓글에도 사용자명에 링크가 걸림 |
111 | 113 |
state: OPEN |
... | ... | @@ -115,7 +117,11 @@ |
115 | 117 |
id: 1 |
116 | 118 |
- !!models.Issue |
117 | 119 |
authorId: 4 |
118 |
- assigneeId: 2 |
|
120 |
+ assignee: !!models.Assignee |
|
121 |
+ user: !!models.User |
|
122 |
+ id: 2 |
|
123 |
+ project: !!models.Project |
|
124 |
+ id: 1 |
|
119 | 125 |
title: gittracker.php의 메모리 제한 에러 |
120 | 126 |
body: 내용 gittracker.php의 메모리 제한 에러 |
121 | 127 |
state: CLOSED |
... | ... | @@ -126,7 +132,11 @@ |
126 | 132 |
numOfComments: 1 |
127 | 133 |
- !!models.Issue |
128 | 134 |
authorId: 4 |
129 |
- assigneeId: 3 |
|
135 |
+ assignee: !!models.Assignee |
|
136 |
+ user: !!models.User |
|
137 |
+ id: 3 |
|
138 |
+ project: !!models.Project |
|
139 |
+ id: 1 |
|
130 | 140 |
title: git/hg 코드 브라우저에 i18n이 적용되지 않음 |
131 | 141 |
body: 내용 git/hg 코드 브라우저에 i18n이 적용되지 않음 |
132 | 142 |
state: CLOSED |
... | ... | @@ -136,7 +146,11 @@ |
136 | 146 |
id: 1 |
137 | 147 |
- !!models.Issue |
138 | 148 |
authorId: 4 |
139 |
- assigneeId: 3 |
|
149 |
+ assignee: !!models.Assignee |
|
150 |
+ user: !!models.User |
|
151 |
+ id: 3 |
|
152 |
+ project: !!models.Project |
|
153 |
+ id: 1 |
|
140 | 154 |
title: CUBRID 설치 문제 |
141 | 155 |
body: IOS는 설치 못하나요? |
142 | 156 |
state: OPEN |
... | ... | @@ -146,7 +160,11 @@ |
146 | 160 |
id: 3 |
147 | 161 |
- !!models.Issue |
148 | 162 |
authorId: 4 |
149 |
- assigneeId: 3 |
|
163 |
+ assignee: !!models.Assignee |
|
164 |
+ user: !!models.User |
|
165 |
+ id: 3 |
|
166 |
+ project: !!models.Project |
|
167 |
+ id: 1 |
|
150 | 168 |
title: 메모리 누수 현상 |
151 | 169 |
body: 메모리가 너무 누수가 되는듯. |
152 | 170 |
state: CLOSED |
... | ... | @@ -156,7 +174,11 @@ |
156 | 174 |
id: 3 |
157 | 175 |
- !!models.Issue |
158 | 176 |
authorId: 4 |
159 |
- assigneeId: 3 |
|
177 |
+ assignee: !!models.Assignee |
|
178 |
+ user: !!models.User |
|
179 |
+ id: 3 |
|
180 |
+ project: !!models.Project |
|
181 |
+ id: 1 |
|
160 | 182 |
title: Client application for Mac. |
161 | 183 |
body: Please make it. |
162 | 184 |
state: OPEN |
... | ... | @@ -166,7 +188,11 @@ |
166 | 188 |
id: 3 |
167 | 189 |
- !!models.Issue |
168 | 190 |
authorId: 4 |
169 |
- assigneeId: 3 |
|
191 |
+ assignee: !!models.Assignee |
|
192 |
+ user: !!models.User |
|
193 |
+ id: 3 |
|
194 |
+ project: !!models.Project |
|
195 |
+ id: 1 |
|
170 | 196 |
title: CPU 무한 점유울 문제. |
171 | 197 |
body: CPU를 무한사용 중이에요. |
172 | 198 |
state: CLOSED |
... | ... | @@ -176,7 +202,11 @@ |
176 | 202 |
id: 3 |
177 | 203 |
- !!models.Issue |
178 | 204 |
authorId: 1 |
179 |
- assigneeId: 3 |
|
205 |
+ assignee: !!models.Assignee |
|
206 |
+ user: !!models.User |
|
207 |
+ id: 3 |
|
208 |
+ project: !!models.Project |
|
209 |
+ id: 1 |
|
180 | 210 |
title: Less chained imports causes compile error |
181 | 211 |
body: When using Play, when I chain less files such as a.less imports b.less which in turn imports c.less |
182 | 212 |
state: CLOSED |
... | ... | @@ -186,7 +216,11 @@ |
186 | 216 |
id: 1 |
187 | 217 |
- !!models.Issue |
188 | 218 |
authorId: 1 |
189 |
- assigneeId: 2 |
|
219 |
+ assignee: !!models.Assignee |
|
220 |
+ user: !!models.User |
|
221 |
+ id: 2 |
|
222 |
+ project: !!models.Project |
|
223 |
+ id: 1 |
|
190 | 224 |
title: Weird TypeDoesNotMatch exception in RC-3 and final |
191 | 225 |
body: The following code works as expected in RC1-Snapshot, but breaks with an TypeDoesNotMatch exception in RC-3 and 2.0 final. |
192 | 226 |
state: CLOSED |
... | ... | @@ -205,7 +239,11 @@ |
205 | 239 |
id: 1 |
206 | 240 |
- !!models.Issue |
207 | 241 |
authorId: 1 |
208 |
- assigneeId: 3 |
|
242 |
+ assignee: !!models.Assignee |
|
243 |
+ user: !!models.User |
|
244 |
+ id: 3 |
|
245 |
+ project: !!models.Project |
|
246 |
+ id: 2 |
|
209 | 247 |
title: Update sbt-idea to 1.1.0 |
210 | 248 |
body: Create sbt project definition module, if exists, for each subproject (pull 128) |
211 | 249 |
state: CLOSED |
... | ... | @@ -215,7 +253,11 @@ |
215 | 253 |
id: 2 |
216 | 254 |
- !!models.Issue |
217 | 255 |
authorId: 1 |
218 |
- assigneeId: 3 |
|
256 |
+ assignee: !!models.Assignee |
|
257 |
+ user: !!models.User |
|
258 |
+ id: 3 |
|
259 |
+ project: !!models.Project |
|
260 |
+ id: 2 |
|
219 | 261 |
title: Support Tuple 22, not just Tuple 18 in api/data/Forms.scala |
220 | 262 |
body: While creating some complex forms, Eclipse started hanging for some unknown reason. |
221 | 263 |
state: CLOSED |
... | ... | @@ -225,7 +267,11 @@ |
225 | 267 |
id: 2 |
226 | 268 |
- !!models.Issue |
227 | 269 |
authorId: 1 |
228 |
- assigneeId: 3 |
|
270 |
+ assignee: !!models.Assignee |
|
271 |
+ user: !!models.User |
|
272 |
+ id: 3 |
|
273 |
+ project: !!models.Project |
|
274 |
+ id: 2 |
|
229 | 275 |
title: Support Tuple 22, not just Tuple 18 in api/data/Forms.scala |
230 | 276 |
body: While creating some complex forms, Eclipse started hanging for some unknown reason. |
231 | 277 |
state: OPEN |
... | ... | @@ -235,7 +281,11 @@ |
235 | 281 |
id: 2 |
236 | 282 |
- !!models.Issue |
237 | 283 |
authorId: 1 |
238 |
- assigneeId: 3 |
|
284 |
+ assignee: !!models.Assignee |
|
285 |
+ user: !!models.User |
|
286 |
+ id: 3 |
|
287 |
+ project: !!models.Project |
|
288 |
+ id: 2 |
|
239 | 289 |
title: Cookie Expires date |
240 | 290 |
body: With Play 2.0.2, when creating a cookie with response().setCookie, HTTP output is like |
241 | 291 |
state: OPEN |
... | ... | @@ -245,7 +295,11 @@ |
245 | 295 |
id: 2 |
246 | 296 |
- !!models.Issue |
247 | 297 |
authorId: 1 |
248 |
- assigneeId: 3 |
|
298 |
+ assignee: !!models.Assignee |
|
299 |
+ user: !!models.User |
|
300 |
+ id: 3 |
|
301 |
+ project: !!models.Project |
|
302 |
+ id: 2 |
|
249 | 303 |
title: require is not work in windows (Closure Compiler) |
250 | 304 |
body: Test on Play2.1-Snapshot |
251 | 305 |
state: OPEN |
... | ... | @@ -255,7 +309,11 @@ |
255 | 309 |
id: 2 |
256 | 310 |
- !!models.Issue |
257 | 311 |
authorId: 2 |
258 |
- assigneeId: 3 |
|
312 |
+ assignee: !!models.Assignee |
|
313 |
+ user: !!models.User |
|
314 |
+ id: 3 |
|
315 |
+ project: !!models.Project |
|
316 |
+ id: 3 |
|
259 | 317 |
title: Remember me is not working |
260 | 318 |
body: Test on Play2.1-Snapshot |
261 | 319 |
state: OPEN |
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?