Merge lp:~abentley/launchpad/bugcomment-as-icomment into lp:launchpad

Proposed by Aaron Bentley on 2012-01-20
Status: Merged
Approved by: Brad Crittenden on 2012-01-20
Approved revision: no longer in the source branch.
Merged at revision: 14739
Proposed branch: lp:~abentley/launchpad/bugcomment-as-icomment
Merge into: lp:launchpad
Prerequisite: lp:~abentley/launchpad/comment-lint
Diff against target: 776 lines (+205/-102)
25 files modified
lib/canonical/launchpad/icing/css/modifiers.css (+2/-3)
lib/lp/bugs/browser/bugcomment.py (+10/-24)
lib/lp/bugs/browser/tests/test_bugcomment.py (+1/-1)
lib/lp/bugs/doc/bugcomment.txt (+1/-1)
lib/lp/bugs/interfaces/bugmessage.py (+3/-7)
lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt (+3/-3)
lib/lp/bugs/stories/bugs/xx-bug-index-lots-of-comments.txt (+3/-3)
lib/lp/bugs/stories/bugs/xx-numbered-comments.txt (+1/-1)
lib/lp/bugs/templates/bugcomment-box.pt (+2/-8)
lib/lp/code/browser/branch.py (+2/-2)
lib/lp/code/browser/branchmergeproposal.py (+6/-3)
lib/lp/code/browser/codereviewcomment.py (+16/-20)
lib/lp/code/browser/configure.zcml (+6/-1)
lib/lp/code/browser/tests/test_branchmergeproposal.py (+17/-1)
lib/lp/code/browser/tests/test_codereviewcomment.py (+8/-1)
lib/lp/code/templates/codereviewcomment-body.pt (+1/-2)
lib/lp/registry/browser/configure.zcml (+5/-1)
lib/lp/registry/browser/distroseriesdifference.py (+14/-10)
lib/lp/scripts/harness.py (+13/-1)
lib/lp/services/comments/browser/configure.zcml (+8/-2)
lib/lp/services/comments/browser/messagecomment.py (+61/-0)
lib/lp/services/comments/doc/conversation.txt (+4/-1)
lib/lp/services/comments/interfaces/conversation.py (+8/-1)
lib/lp/services/comments/templates/comment-body.pt (+5/-1)
lib/lp/testing/factory.py (+5/-4)
To merge this branch: bzr merge lp:~abentley/launchpad/bugcomment-as-icomment
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code 2012-01-20 Approve on 2012-01-20
Review via email: mp+89497@code.launchpad.net

Commit Message

Use common rendering for BugComment and CodeReviewComment body text.

Description of the Change

= Summary =
Truncate long code review comments and provide "Read more..." link.

== Proposed fix ==
Use common rendering for BugComment and CodeReviewComment

== Pre-implementation notes ==
Discussed with deryck

== Implementation details ==
Provide MessageComment to implement IComment in terms of IMessage, and use in CodeReivewComment, BugComment, DistroSeriesDifferenceDisplayComment. Provide adapters to convert CodeReviewComment and DistroSeriesDifferenceDisplayComment into IMessage.

Move the code for truncation/Read More from bug comments to lp.services.comments.

IComment gains too_long and text_for_display attributes. MessageComment implements them.

Support truncation to 3200 chars in CodeReviewDisplayComment.

Adjust CodeReviewDisplayComment call sites to specify whether to truncate.

Use a single "comment-text" css class, instead of multiple ones.

Add browser_open to harness.py, to simplify interactive testing.

== Tests ==
bin/test -t test_bugcomment -t bugcomment.txt -t xx-bug-comments-truncated.txt -t xx-bug-index-lots-of-comments.txt -t xx-numbered-comments.txt -t test_branchmergeproposal -t test_codereviewcomment -t conversation.txt

== Demo and Q/A ==
Create a code review comment longer than 3200 chars. It should be truncated and have a "Read more" link. That link should display the full code review comment.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/services/comments/browser/configure.zcml
  lib/lp/registry/browser/configure.zcml
  lib/lp/bugs/browser/tests/test_bugcomment.py
  lib/lp/code/browser/configure.zcml
  lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt
  lib/lp/code/browser/branch.py
  lib/lp/code/templates/codereviewcomment-body.pt
  lib/canonical/launchpad/icing/css/modifiers.css
  lib/lp/code/browser/tests/test_branchmergeproposal.py
  lib/lp/code/browser/tests/test_codereviewcomment.py
  lib/lp/bugs/browser/bugcomment.py
  lib/lp/services/comments/browser/messagecomment.py
  lib/lp/registry/browser/distroseriesdifference.py
  lib/lp/testing/factory.py
  lib/lp/services/comments/interfaces/conversation.py
  lib/lp/code/browser/codereviewcomment.py
  lib/lp/bugs/interfaces/bugmessage.py
  lib/lp/scripts/harness.py
  lib/lp/bugs/stories/bugs/xx-numbered-comments.txt
  lib/lp/services/comments/templates/comment-body.pt
  lib/lp/bugs/doc/bugcomment.txt
  lib/lp/services/comments/doc/conversation.txt
  lib/lp/code/browser/branchmergeproposal.py
  lib/lp/bugs/templates/bugcomment-box.pt
  lib/lp/bugs/stories/bugs/xx-bug-index-lots-of-comments.txt

./lib/canonical/launchpad/icing/css/modifiers.css
      61: Unknown Property name.: filter
      62: Unknown Property name.: -ms-filter
     140: Unknown Property name.: filter
     141: Unknown Property name.: -ms-filter
      61: I009: Wrong separator on property: value pair.
     140: I009: Wrong separator on property: value pair.
./lib/lp/scripts/harness.py
      26: 'from storm.expr import *' used; unable to detect undefined names
      28: 'from storm.locals import *' used; unable to detect undefined names
      21: 'rlcompleter' imported but unused

To post a comment you must log in.
Brad Crittenden (bac) wrote :

Aaron this is a really nice branch. It was very readable and understandable. Thanks for the nice work. I've got no suggested changes or questions.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/icing/css/modifiers.css'
2--- lib/canonical/launchpad/icing/css/modifiers.css 2012-01-26 14:20:33 +0000
3+++ lib/canonical/launchpad/icing/css/modifiers.css 2012-01-26 14:20:34 +0000
4@@ -155,9 +155,8 @@
5
6 pre.changelog,
7 table.diff,
8-.bug-comment,
9-.bug-activity,
10-.codereviewcomment {
11+.comment-text,
12+.bug-activity {
13 font-family: 'Ubuntu Mono', monospace;
14 }
15
16
17=== modified file 'lib/lp/bugs/browser/bugcomment.py'
18--- lib/lp/bugs/browser/bugcomment.py 2012-01-06 19:45:52 +0000
19+++ lib/lp/bugs/browser/bugcomment.py 2012-01-26 14:20:34 +0000
20@@ -1,4 +1,4 @@
21-# Copyright 2009 Canonical Ltd. This software is licensed under the
22+# Copyright 2006-2012 Canonical Ltd. This software is licensed under the
23 # GNU Affero General Public License version 3 (see the file LICENSE).
24
25 """Bug comment browser view classes."""
26@@ -41,6 +41,7 @@
27
28 from lp.bugs.interfaces.bugattachment import BugAttachmentType
29 from lp.bugs.interfaces.bugmessage import IBugComment
30+from lp.services.comments.browser.messagecomment import MessageComment
31 from lp.services.config import config
32 from lp.services.features import getFeatureFlag
33 from lp.services.librarian.browser import ProxiedLibraryFileAlias
34@@ -176,7 +177,7 @@
35 yield [event for (kind, event) in window_group]
36
37
38-class BugComment:
39+class BugComment(MessageComment):
40 """Data structure that holds all data pertaining to a bug comment.
41
42 It keeps track of which index it has in the bug comment list and
43@@ -194,6 +195,11 @@
44 def __init__(
45 self, index, message, bugtask, activity=None,
46 show_spam_controls=False, user=None, display='full'):
47+ if display == 'truncate':
48+ comment_limit = config.malone.max_comment_size
49+ else:
50+ comment_limit = None
51+ super(BugComment, self).__init__(comment_limit)
52
53 self.index = index
54 self.bugtask = bugtask
55@@ -216,10 +222,6 @@
56 if bool(getFeatureFlag(flag)):
57 user_owns_comment = user is not None and user == self.owner
58 self.show_spam_controls = show_spam_controls or user_owns_comment
59- if display == 'truncate':
60- self.comment_limit = config.malone.max_comment_size
61- else:
62- self.comment_limit = None
63 self.hide_text = (display == 'hide')
64
65 @cachedproperty
66@@ -239,28 +241,12 @@
67 """
68 return not self.visible
69
70- @property
71- def needs_truncation(self):
72- if self.comment_limit is None:
73- return False
74- return len(self.text_contents) > self.comment_limit
75-
76- @property
77- def was_truncated(self):
78- return self.needs_truncation
79-
80 @cachedproperty
81 def text_for_display(self):
82 if self.hide_text:
83 return ''
84- if not self.needs_truncation:
85- return self.text_contents
86- # Note here that we truncate at comment_limit, and not
87- # comment_limit - 3; while it would be nice to account for
88- # the ellipsis, this breaks down when the comment limit is
89- # less than 3 (which can happen in a testcase) and it makes
90- # counting the strings harder.
91- return "%s..." % self.text_contents[:self.comment_limit]
92+ else:
93+ return super(BugComment, self).text_for_display
94
95 def isIdenticalTo(self, other):
96 """Compare this BugComment to another and return True if they are
97
98=== modified file 'lib/lp/bugs/browser/tests/test_bugcomment.py'
99--- lib/lp/bugs/browser/tests/test_bugcomment.py 2012-01-06 19:45:52 +0000
100+++ lib/lp/bugs/browser/tests/test_bugcomment.py 2012-01-26 14:20:34 +0000
101@@ -1,4 +1,4 @@
102-# Copyright 2010 Canonical Ltd. This software is licensed under the
103+# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
104 # GNU Affero General Public License version 3 (see the file LICENSE).
105
106 """Tests for the bugcomment module."""
107
108=== modified file 'lib/lp/bugs/doc/bugcomment.txt'
109--- lib/lp/bugs/doc/bugcomment.txt 2012-01-26 14:20:33 +0000
110+++ lib/lp/bugs/doc/bugcomment.txt 2012-01-26 14:20:34 +0000
111@@ -115,7 +115,7 @@
112 If we get the bug comments from the view we can see that the two additional
113 comments have been truncated:
114
115- >>> [(bug_comment.index, bug_comment.was_truncated)
116+ >>> [(bug_comment.index, bug_comment.too_long)
117 ... for bug_comment in bug_comments(bug_view)]
118 [(1, True), (2, True)]
119
120
121=== modified file 'lib/lp/bugs/interfaces/bugmessage.py'
122--- lib/lp/bugs/interfaces/bugmessage.py 2012-01-01 02:58:52 +0000
123+++ lib/lp/bugs/interfaces/bugmessage.py 2012-01-26 14:20:34 +0000
124@@ -1,4 +1,4 @@
125-# Copyright 2009 Canonical Ltd. This software is licensed under the
126+# Copyright 2004-2012 Canonical Ltd. This software is licensed under the
127 # GNU Affero General Public License version 3 (see the file LICENSE).
128
129 # pylint: disable-msg=E0211,E0213
130@@ -32,6 +32,7 @@
131 from lp.bugs.interfaces.hasbug import IHasBug
132 from lp.registry.interfaces.person import IPerson
133 from lp.services.fields import Title
134+from lp.services.comments.interfaces.conversation import IComment
135 from lp.services.messages.interfaces.message import IMessage
136
137
138@@ -113,7 +114,7 @@
139 required=False, default=None)
140
141
142-class IBugComment(IMessage):
143+class IBugComment(IMessage, IComment):
144 """A bug comment for displaying in the web UI."""
145
146 bugtask = Attribute(
147@@ -127,11 +128,6 @@
148 title=u'A hidden comment still displayed for admins.',
149 readonly=True)
150 index = Int(title=u'The comment number', required=True, readonly=True)
151- was_truncated = Bool(
152- title=u'Whether the displayed text was truncated for display.',
153- readonly=True)
154- text_for_display = Text(
155- title=u'The comment text to be displayed in the UI.', readonly=True)
156 display_title = Attribute('Whether or not to show the title.')
157 synchronized = Attribute(
158 'Has the comment been synchronized with a remote bug tracker?')
159
160=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt'
161--- lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt 2012-01-26 14:20:33 +0000
162+++ lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt 2012-01-26 14:20:34 +0000
163@@ -32,14 +32,14 @@
164 ... print div_tag
165 >>> print_comments(browser.contents) #doctest: -ELLIPSIS
166 <div class="boardCommentBody">
167- <div class="bug-comment" itemprop="commentText"><p>This
168+ <div class="comment-text" itemprop="commentText"><p>This
169 would be a real killer feature. If there...</p></div>
170 <p>
171 <a href="/tomcat/+bug/2/comments/1">Read more...</a>
172 </p>
173 </div>
174 <div class="boardCommentBody">
175- <div class="bug-comment" itemprop="commentText"><p>Oddly
176+ <div class="comment-text" itemprop="commentText"><p>Oddly
177 enough the bug system seems only capabl...</p></div>
178 <p>
179 <a href="/tomcat/+bug/2/comments/2">Read more...</a>
180@@ -59,7 +59,7 @@
181
182 >>> print_comments(browser.contents) #doctest: -ELLIPSIS
183 <div class="boardCommentBody">
184- <div class="bug-comment" itemprop="commentText"><p>This
185+ <div class="comment-text" itemprop="commentText"><p>This
186 would be a real killer feature. If there is already code
187 to make it possible, why aren't there tons of press announcements
188 about the secuirty possibilities. Imagine - no more embarrassing
189
190=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-index-lots-of-comments.txt'
191--- lib/lp/bugs/stories/bugs/xx-bug-index-lots-of-comments.txt 2011-12-29 05:29:36 +0000
192+++ lib/lp/bugs/stories/bugs/xx-bug-index-lots-of-comments.txt 2012-01-26 14:20:34 +0000
193@@ -8,7 +8,7 @@
194 Bug 11 has quite a few comments.
195
196 >>> user_browser.open('http://launchpad.dev/bugs/11')
197- >>> comments = find_tags_by_class(user_browser.contents, 'bug-comment')
198+ >>> comments = find_tags_by_class(user_browser.contents, 'comment-text')
199 >>> len(comments)
200 6
201
202@@ -26,7 +26,7 @@
203 Now only 3 comments will be displayed; the oldest and the 2 newest.
204
205 >>> user_browser.open('http://launchpad.dev/bugs/11')
206- >>> comments = find_tags_by_class(user_browser.contents, 'bug-comment')
207+ >>> comments = find_tags_by_class(user_browser.contents, 'comment-text')
208 >>> len(comments)
209 3
210
211@@ -59,7 +59,7 @@
212 'http://.../jokosher/+bug/11?comments=all'
213
214 >>> user_browser.getLink('View all 6 comments').click()
215- >>> comments = find_tags_by_class(user_browser.contents, 'bug-comment')
216+ >>> comments = find_tags_by_class(user_browser.contents, 'comment-text')
217 >>> len(comments)
218 6
219
220
221=== modified file 'lib/lp/bugs/stories/bugs/xx-numbered-comments.txt'
222--- lib/lp/bugs/stories/bugs/xx-numbered-comments.txt 2011-10-20 00:53:01 +0000
223+++ lib/lp/bugs/stories/bugs/xx-numbered-comments.txt 2012-01-26 14:20:34 +0000
224@@ -13,7 +13,7 @@
225 ... number_node = comment.find(None, 'bug-comment-index')
226 ... person_node = comment.find(
227 ... lambda node: 'person' in node.get('class', ''))
228- ... comment_node = comment.find(None, 'bug-comment')
229+ ... comment_node = comment.find(None, 'comment-text')
230 ... print "%s: %s\n %s" % (
231 ... extract_text(number_node),
232 ... extract_text(person_node),
233
234=== modified file 'lib/lp/bugs/templates/bugcomment-box.pt'
235--- lib/lp/bugs/templates/bugcomment-box.pt 2012-01-11 10:07:56 +0000
236+++ lib/lp/bugs/templates/bugcomment-box.pt 2012-01-26 14:20:34 +0000
237@@ -90,14 +90,8 @@
238 </li>
239 </ul>
240
241- <div class="bug-comment" itemprop="commentText"
242- tal:content="structure
243- comment/text_for_display/fmt:obfuscate-email/fmt:email-to-html">
244- Comment text.
245- </div>
246- <p tal:condition="comment/was_truncated">
247- <a tal:attributes="href comment/fmt:url">Read more...</a>
248- </p>
249+ <tal:text
250+ replace="structure comment/@@+comment-body-text" />
251 </div>
252
253 <div class="boardCommentFooter" tal:condition="comment/show_footer">
254
255=== modified file 'lib/lp/code/browser/branch.py'
256--- lib/lp/code/browser/branch.py 2011-12-30 06:14:56 +0000
257+++ lib/lp/code/browser/branch.py 2012-01-26 14:20:34 +0000
258@@ -1,4 +1,4 @@
259-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
260+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
261 # GNU Affero General Public License version 3 (see the file LICENSE).
262
263 """Branch views."""
264@@ -1314,7 +1314,7 @@
265 for_input = True
266
267 custom_widget('target_branch', TargetBranchWidget)
268- custom_widget('comment', TextAreaWidget, cssClass='codereviewcomment')
269+ custom_widget('comment', TextAreaWidget, cssClass='comment-text')
270
271 page_title = label = 'Propose branch for merging'
272
273
274=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
275--- lib/lp/code/browser/branchmergeproposal.py 2012-01-01 02:58:52 +0000
276+++ lib/lp/code/browser/branchmergeproposal.py 2012-01-26 14:20:34 +0000
277@@ -1,4 +1,4 @@
278-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
279+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
280 # GNU Affero General Public License version 3 (see the file LICENSE).
281
282 # pylint: disable-msg=C0322,F0401
283@@ -573,6 +573,8 @@
284 self.extra_css_class = None
285 self.comment_author = None
286 self.body_text = None
287+ self.text_for_display = None
288+ self.too_long = False
289 self.comment_date = None
290 self.display_attachments = False
291
292@@ -648,7 +650,8 @@
293 while merge_proposal is not None:
294 from_superseded = merge_proposal != self.context
295 comments.extend(
296- CodeReviewDisplayComment(comment, from_superseded)
297+ CodeReviewDisplayComment(
298+ comment, from_superseded, limit_length=True)
299 for comment in merge_proposal.all_comments)
300 merge_proposal = merge_proposal.supersedes
301 comments = sorted(comments, key=operator.attrgetter('date'))
302@@ -1426,7 +1429,7 @@
303 schema = IAddVote
304 field_names = ['vote', 'review_type', 'comment']
305
306- custom_widget('comment', TextAreaWidget, cssClass='codereviewcomment')
307+ custom_widget('comment', TextAreaWidget, cssClass='comment-text')
308
309 @cachedproperty
310 def initial_values(self):
311
312=== modified file 'lib/lp/code/browser/codereviewcomment.py'
313--- lib/lp/code/browser/codereviewcomment.py 2012-01-01 02:58:52 +0000
314+++ lib/lp/code/browser/codereviewcomment.py 2012-01-26 14:20:34 +0000
315@@ -1,4 +1,4 @@
316-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
317+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
318 # GNU Affero General Public License version 3 (see the file LICENSE).
319
320 __metaclass__ = type
321@@ -33,6 +33,7 @@
322 from lp.code.interfaces.codereviewcomment import ICodeReviewComment
323 from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
324 from lp.services.comments.interfaces.conversation import IComment
325+from lp.services.comments.browser.messagecomment import MessageComment
326 from lp.services.config import config
327 from lp.services.librarian.interfaces import ILibraryFileAlias
328 from lp.services.propertycache import (
329@@ -52,7 +53,7 @@
330 """Marker interface for displaying code review comments."""
331
332
333-class CodeReviewDisplayComment:
334+class CodeReviewDisplayComment(MessageComment):
335 """A code review comment or activity or both.
336
337 The CodeReviewComment itself does not implement the IComment interface as
338@@ -64,7 +65,12 @@
339
340 delegates(ICodeReviewComment, 'comment')
341
342- def __init__(self, comment, from_superseded=False):
343+ def __init__(self, comment, from_superseded=False, limit_length=True):
344+ if limit_length:
345+ comment_limit = config.malone.max_comment_size
346+ else:
347+ comment_limit = None
348+ super(CodeReviewDisplayComment, self).__init__(comment_limit)
349 self.comment = comment
350 get_property_cache(self).has_body = bool(self.comment.message_body)
351 self.has_footer = self.comment.vote is not None
352@@ -80,26 +86,11 @@
353 return ''
354
355 @cachedproperty
356- def comment_author(self):
357- """The author of the comment."""
358- return self.comment.message.owner
359-
360- @cachedproperty
361- def has_body(self):
362- """Is there body text?"""
363- return bool(self.body_text)
364-
365- @cachedproperty
366 def body_text(self):
367 """Get the body text for the message."""
368 return self.comment.message_body
369
370 @cachedproperty
371- def comment_date(self):
372- """The date of the comment."""
373- return self.comment.message.datecreated
374-
375- @cachedproperty
376 def all_attachments(self):
377 return self.comment.getAttachments()
378
379@@ -114,6 +105,11 @@
380 return self.all_attachments[1]
381
382
383+def get_message(display_comment):
384+ """Adapt an ICodeReviwComment to an IMessage."""
385+ return display_comment.comment.message
386+
387+
388 class CodeReviewCommentPrimaryContext:
389 """The primary context is the comment is that of the source branch."""
390
391@@ -173,7 +169,7 @@
392 @cachedproperty
393 def comment(self):
394 """The decorated code review comment."""
395- return CodeReviewDisplayComment(self.context)
396+ return CodeReviewDisplayComment(self.context, limit_length=False)
397
398 @property
399 def page_description(self):
400@@ -239,7 +235,7 @@
401
402 schema = IEditCodeReviewComment
403
404- custom_widget('comment', TextAreaWidget, cssClass='codereviewcomment')
405+ custom_widget('comment', TextAreaWidget, cssClass='comment-text')
406 custom_widget('vote', MyDropWidget)
407
408 page_title = 'Reply to code review comment'
409
410=== modified file 'lib/lp/code/browser/configure.zcml'
411--- lib/lp/code/browser/configure.zcml 2012-01-09 11:36:12 +0000
412+++ lib/lp/code/browser/configure.zcml 2012-01-26 14:20:34 +0000
413@@ -1,4 +1,4 @@
414-<!-- Copyright 2009-2011 Canonical Ltd. This software is licensed under the
415+<!-- Copyright 2009-2012 Canonical Ltd. This software is licensed under the
416 GNU Affero General Public License version 3 (see the file LICENSE).
417 -->
418
419@@ -666,6 +666,11 @@
420 name="+comment-footer"
421 template="../templates/codereviewcomment-footer.pt"/>
422 </browser:pages>
423+ <adapter
424+ provides="lp.services.messages.interfaces.message.IMessage"
425+ for="lp.code.browser.codereviewcomment.ICodeReviewDisplayComment"
426+ factory="lp.code.browser.codereviewcomment.get_message"/>
427+
428 <browser:pages
429 for="lp.code.browser.branchmergeproposal.ICodeReviewNewRevisions"
430 layer="lp.code.publisher.CodeLayer"
431
432=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
433--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2012-01-01 02:58:52 +0000
434+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2012-01-26 14:20:34 +0000
435@@ -1,4 +1,4 @@
436-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
437+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
438 # GNU Affero General Public License version 3 (see the file LICENSE).
439
440 # pylint: disable-msg=F0401
441@@ -1114,6 +1114,22 @@
442 "automatically when ready.",
443 browser.contents)
444
445+ def test_short_conversation_comments_not_truncated(self):
446+ """Short comments should not be truncated."""
447+ comment = self.factory.makeCodeReviewComment(body='x y' * 100)
448+ browser = self.getViewBrowser(comment.branch_merge_proposal)
449+ self.assertIn('x y' * 100, browser.contents)
450+
451+ def test_long_conversation_comments_truncated(self):
452+ """Long comments in a conversation should be truncated."""
453+ comment = self.factory.makeCodeReviewComment(body='x y' * 2000)
454+ url = canonical_url(comment, force_local_path=True)
455+ browser = self.getViewBrowser(comment.branch_merge_proposal)
456+ self.assertNotIn('x y' * 2000, browser.contents)
457+ read_more = Tag(
458+ 'Read more link', 'a', {'href': url}, text='Read more...')
459+ self.assertThat(browser.contents, HTMLContains(read_more))
460+
461
462 class TestLatestProposalsForEachBranch(TestCaseWithFactory):
463 """Confirm that the latest branch is returned."""
464
465=== modified file 'lib/lp/code/browser/tests/test_codereviewcomment.py'
466--- lib/lp/code/browser/tests/test_codereviewcomment.py 2012-01-01 02:58:52 +0000
467+++ lib/lp/code/browser/tests/test_codereviewcomment.py 2012-01-26 14:20:34 +0000
468@@ -1,4 +1,4 @@
469-# Copyright 2009 Canonical Ltd. This software is licensed under the
470+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
471 # GNU Affero General Public License version 3 (see the file LICENSE).
472
473 """Unit tests for CodeReviewComments."""
474@@ -70,3 +70,10 @@
475 dict(
476 name='description',
477 content=comment.message_body))))
478+
479+ def test_long_comments_not_truncated(self):
480+ """Long comments displayed by themselves are not truncated."""
481+ comment = self.factory.makeCodeReviewComment(body='x y' * 2000)
482+ browser = self.getViewBrowser(comment)
483+ body = Tag('Body text', 'p', text='x y' * 2000)
484+ self.assertThat(browser.contents, HTMLContains(body))
485
486=== modified file 'lib/lp/code/templates/codereviewcomment-body.pt'
487--- lib/lp/code/templates/codereviewcomment-body.pt 2011-12-22 09:05:46 +0000
488+++ lib/lp/code/templates/codereviewcomment-body.pt 2012-01-26 14:20:34 +0000
489@@ -3,8 +3,7 @@
490 xmlns:metal="http://xml.zope.org/namespaces/metal"
491 omit-tag="">
492
493- <tal:message replace="structure view/comment/body_text/fmt:obfuscate-email/fmt:nice_pre" />
494-
495+ <div tal:content="structure context/@@+comment-body-text" />
496 <tal:good-attachments repeat="attachment view/comment/display_attachments">
497 <div class="boardComment attachment">
498 <div class="boardCommentDetails filename"><a tal:content="attachment/filename" tal:attributes="href attachment/getURL"/></div>
499
500=== modified file 'lib/lp/registry/browser/configure.zcml'
501--- lib/lp/registry/browser/configure.zcml 2012-01-18 16:49:09 +0000
502+++ lib/lp/registry/browser/configure.zcml 2012-01-26 14:20:34 +0000
503@@ -1,4 +1,4 @@
504-<!-- Copyright 2009-2011 Canonical Ltd. This software is licensed under the
505+<!-- Copyright 2009-2012 Canonical Ltd. This software is licensed under the
506 GNU Affero General Public License version 3 (see the file LICENSE).
507 -->
508
509@@ -2238,4 +2238,8 @@
510 attribute_to_parent="owner" />
511
512 </facet>
513+<adapter
514+ provides="lp.services.messages.interfaces.message.IMessage"
515+ for="lp.registry.browser.distroseriesdifference.IDistroSeriesDifferenceDisplayComment"
516+ factory="lp.registry.browser.distroseriesdifference.get_message"/>
517 </configure>
518
519=== modified file 'lib/lp/registry/browser/distroseriesdifference.py'
520--- lib/lp/registry/browser/distroseriesdifference.py 2012-01-17 21:45:24 +0000
521+++ lib/lp/registry/browser/distroseriesdifference.py 2012-01-26 14:20:34 +0000
522@@ -45,6 +45,7 @@
523 IComment,
524 IConversation,
525 )
526+from lp.services.comments.browser.messagecomment import MessageComment
527 from lp.services.propertycache import cachedproperty
528 from lp.services.webapp import (
529 LaunchpadView,
530@@ -241,20 +242,23 @@
531 self.show_package_diffs_request_link)
532
533
534-class DistroSeriesDifferenceDisplayComment:
535+class IDistroSeriesDifferenceDisplayComment(IComment):
536+ """Marker interface."""
537+
538+
539+class DistroSeriesDifferenceDisplayComment(MessageComment):
540 """Used simply to provide `IComment` for rendering."""
541- implements(IComment)
542-
543- has_body = True
544- has_footer = False
545- display_attachments = False
546- extra_css_class = ''
547+ implements(IDistroSeriesDifferenceDisplayComment)
548
549 def __init__(self, comment):
550 """Setup the attributes required by `IComment`."""
551- self.comment_author = comment.comment_author
552- self.comment_date = comment.comment_date
553- self.body_text = comment.body_text
554+ super(DistroSeriesDifferenceDisplayComment, self).__init__(None)
555+ self.comment = comment
556+
557+
558+def get_message(comment):
559+ """Adapter from IDistroSeriesDifferenceDisplayComment to IMessage."""
560+ return comment.comment.message
561
562
563 class CommentXHTMLRepresentation(LaunchpadView):
564
565=== modified file 'lib/lp/scripts/harness.py'
566--- lib/lp/scripts/harness.py 2012-01-26 14:20:33 +0000
567+++ lib/lp/scripts/harness.py 2012-01-26 14:20:34 +0000
568@@ -1,4 +1,4 @@
569-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
570+# Copyright 2004-2012 Canonical Ltd. This software is licensed under the
571 # GNU Affero General Public License version 3 (see the file LICENSE).
572
573 """Scripts for starting a Python prompt with Launchpad initialized.
574@@ -20,6 +20,7 @@
575 import readline
576 import rlcompleter
577 import sys
578+import webbrowser
579
580 from pytz import utc
581 from storm.expr import *
582@@ -87,6 +88,17 @@
583 # Having a factory instance is handy.
584 factory = LaunchpadObjectFactory()
585
586+ def browser_open(obj, *args, **kwargs):
587+ """Open a (possibly newly-created) object's view in a web browser.
588+
589+ Accepts the same parameters as canonical_url.
590+
591+ Performs a commit before invoking the browser, so
592+ "browser_open(factory.makeFoo())" works.
593+ """
594+ transaction.commit()
595+ webbrowser.open(canonical_url(obj, *args, **kwargs))
596+
597 # Silence unused name warnings
598 factory, store
599
600
601=== modified file 'lib/lp/services/comments/browser/configure.zcml'
602--- lib/lp/services/comments/browser/configure.zcml 2010-09-17 11:42:29 +0000
603+++ lib/lp/services/comments/browser/configure.zcml 2012-01-26 14:20:34 +0000
604@@ -1,5 +1,6 @@
605-<!-- Copyright 2009 Canonical Ltd. This software is licensed under the
606- GNU Affero General Public License version 3 (see the file LICENSE).
607+<!-- Copyright 2009, 2010, 2012 Canonical Ltd. This software is licensed
608+ under the GNU Affero General Public License version 3 (see the file
609+ LICENSE).
610 -->
611
612 <configure
613@@ -24,9 +25,14 @@
614 <browser:page
615 name="+comment-header"
616 template="../templates/comment-header.pt"/>
617+ <!-- Default comment body is just the body text, but implementations may
618+ vary -->
619 <browser:page
620 name="+comment-body"
621 template="../templates/comment-body.pt"/>
622+ <browser:page
623+ name="+comment-body-text"
624+ template="../templates/comment-body.pt"/>
625 </browser:pages>
626
627 </configure>
628
629=== added file 'lib/lp/services/comments/browser/messagecomment.py'
630--- lib/lp/services/comments/browser/messagecomment.py 1970-01-01 00:00:00 +0000
631+++ lib/lp/services/comments/browser/messagecomment.py 2012-01-26 14:20:34 +0000
632@@ -0,0 +1,61 @@
633+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
634+# GNU Affero General Public License version 3 (see the file LICENSE).
635+
636+__metaclass__ = type
637+
638+__all__ = ['MessageComment']
639+
640+
641+from lp.services.messages.interfaces.message import IMessage
642+from lp.services.propertycache import cachedproperty
643+
644+
645+class MessageComment:
646+ """Mixin to partially implement IComment in terms of IMessage."""
647+
648+ extra_css_class = ''
649+
650+ has_footer = False
651+
652+ def __init__(self, comment_limit):
653+ self.comment_limit = comment_limit
654+
655+ @property
656+ def display_attachments(self):
657+ return []
658+
659+ @cachedproperty
660+ def comment_author(self):
661+ """The author of the comment."""
662+ return IMessage(self).owner
663+
664+ @cachedproperty
665+ def has_body(self):
666+ """Is there body text?"""
667+ return bool(self.body_text)
668+
669+ @cachedproperty
670+ def comment_date(self):
671+ """The date of the comment."""
672+ return IMessage(self).datecreated
673+
674+ @property
675+ def body_text(self):
676+ return IMessage(self).text_contents
677+
678+ @property
679+ def too_long(self):
680+ if self.comment_limit is None:
681+ return False
682+ return len(self.body_text) > self.comment_limit
683+
684+ @cachedproperty
685+ def text_for_display(self):
686+ if not self.too_long:
687+ return self.body_text
688+ # Note here that we truncate at comment_limit, and not
689+ # comment_limit - 3; while it would be nice to account for
690+ # the ellipsis, this breaks down when the comment limit is
691+ # less than 3 (which can happen in a testcase) and it makes
692+ # counting the strings harder.
693+ return "%s..." % self.body_text[:self.comment_limit]
694
695=== modified file 'lib/lp/services/comments/doc/conversation.txt'
696--- lib/lp/services/comments/doc/conversation.txt 2012-01-26 14:20:33 +0000
697+++ lib/lp/services/comments/doc/conversation.txt 2012-01-26 14:20:34 +0000
698@@ -36,7 +36,10 @@
699 * +comment-header - the top part of the comment
700 normally something like "Bob wrote 4 seconds ago"
701 * +comment-body - the main content
702- Only rendered if IComment.has_body is true.
703+ Only rendered if IComment.has_body is true. By default, is the same as
704+ +comment-body-text
705+ * +comment-body-text - the main content
706+ The textual portion of the message body, suitably escaped and linkified.
707 * +comment-footer - associated activity or other footer info, like
708 bug activity or code reviews.
709 Only rendered if IComment.has_footer is true
710
711=== modified file 'lib/lp/services/comments/interfaces/conversation.py'
712--- lib/lp/services/comments/interfaces/conversation.py 2011-12-24 16:54:44 +0000
713+++ lib/lp/services/comments/interfaces/conversation.py 2012-01-26 14:20:34 +0000
714@@ -1,4 +1,4 @@
715-# Copyright 2009 Canonical Ltd. This software is licensed under the
716+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
717 # GNU Affero General Public License version 3 (see the file LICENSE).
718
719 """Interfaces to do with conversations on Launchpad entities."""
720@@ -39,6 +39,13 @@
721 description=_("Does the comment have a footer?"),
722 readonly=True)
723
724+ too_long = Bool(
725+ title=u'Whether the comment body is too long to display in full.',
726+ readonly=True)
727+
728+ text_for_display = Text(
729+ title=u'The comment text to be displayed in the UI.', readonly=True)
730+
731 body_text = Text(
732 description=_("The body text of the comment."),
733 readonly=True)
734
735=== modified file 'lib/lp/services/comments/templates/comment-body.pt'
736--- lib/lp/services/comments/templates/comment-body.pt 2010-09-17 10:50:51 +0000
737+++ lib/lp/services/comments/templates/comment-body.pt 2012-01-26 14:20:34 +0000
738@@ -3,6 +3,10 @@
739 xmlns:metal="http://xml.zope.org/namespaces/metal"
740 omit-tag="">
741
742- <tal:message replace="structure context/body_text/fmt:obfuscate-email/fmt:nice_pre" />
743+ <div class="comment-text" itemprop="commentText" tal:content="structure
744+ context/text_for_display/fmt:obfuscate-email/fmt:email-to-html" />
745+ <p tal:condition="context/too_long">
746+ <a tal:attributes="href context/fmt:url">Read more...</a>
747+ </p>
748
749 </tal:root>
750
751=== modified file 'lib/lp/testing/factory.py'
752--- lib/lp/testing/factory.py 2012-01-19 14:36:19 +0000
753+++ lib/lp/testing/factory.py 2012-01-26 14:20:34 +0000
754@@ -2,7 +2,7 @@
755 # NOTE: The first line above must stay first; do not move the copyright
756 # notice to the top. See http://www.python.org/dev/peps/pep-0263/.
757 #
758-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
759+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
760 # GNU Affero General Public License version 3 (see the file LICENSE).
761
762 # pylint: disable-msg=F0401
763@@ -2323,9 +2323,10 @@
764 else:
765 merge_proposal = self.makeBranchMergeProposal(
766 registrant=sender)
767- return merge_proposal.createComment(
768- sender, subject, body, vote, vote_tag, parent,
769- _date_created=date_created)
770+ with person_logged_in(sender):
771+ return merge_proposal.createComment(
772+ sender, subject, body, vote, vote_tag, parent,
773+ _date_created=date_created)
774
775 def makeCodeReviewVoteReference(self):
776 bmp = removeSecurityProxy(self.makeBranchMergeProposal())