Merge lp:~sinzui/launchpad/wax-and-wane into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Merged at revision: not available
Proposed branch: lp:~sinzui/launchpad/wax-and-wane
Merge into: lp:launchpad
Diff against target: 296 lines (+110/-63)
8 files modified
lib/canonical/launchpad/icing/style-3-0.css (+3/-0)
lib/lp/registry/browser/mailinglists.py (+13/-4)
lib/lp/registry/browser/milestone.py (+1/-1)
lib/lp/registry/browser/tests/mailinglist-message-views.txt (+74/-0)
lib/lp/registry/browser/tests/milestone-views.txt (+15/-2)
lib/lp/registry/browser/tests/test_views.py (+1/-0)
lib/lp/registry/doc/message-holds.txt (+0/-52)
lib/lp/registry/templates/milestone-index.pt (+3/-4)
To merge this branch: bzr merge lp:~sinzui/launchpad/wax-and-wane
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+17141@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (5.1 KiB)

This is my branch to fix from simple nuisances in launchpad.

    lp:~sinzui/launchpad/wax-and-wane
    Diff size: 206
    Launchpad bug: https://bugs.launchpad.net/bugs/wax-and-wane
    Test command: ./bin/test -vv \
        -t mailinglist-message-views
        -t milestone-views
    Pre-implementation: no one
    Target release: 10.01

Fix from simple nuisances in launchpad
--------------------------------------------------------------------

Bug #504024 [invalid list moderation markup breaks webkit]
    The HeldMessageView and message-moderation.pt create ill-formed markup
    that makes the message moderation page unusable in webkit browsers. I
    have observed two issues:

    1) The template uses `structure view/author`, but the author field is not
    HTML. The angle brackets are not escaped, creating an unclosed tag.

    2) The view's initialize() method is using `paragraphs.append('\n<p>\n')`
    which creates an unclosed paragraph. (this markup was deprecated in 1994
    and it invalid XHTML which the page claims to be).

    Both of these errors can cause form submittal errors.

Bug #505919 [milestones assignees wrap poorly]
    The assignee name wraps to the next line and it is difficult to see
    understand that the number belongs to the next name.

Bug #505917 [Sort milestone bugs by status by default]
    People working the bugs need to see status, then importance.

Rules
-----

Bug #504024 [invalid list moderation markup breaks webkit]
    * Add a <p> before each added paragraph and change the markup added
      afterwards to </p>
    * Remove the `structure` command

Bug #505919 [milestones assignees wrap poorly]
    * Add a CSS class to prevent the count and name from wrapping.
    * ADDENDUM: white-space: nowrap does not work because the comma is
      allowed to wrap. The pre value works if the markup is has no line breaks

Bug #505917 [Sort milestone bugs by status by default]
    * Change the sort order in the view.

QA
--

Bug #504024 [invalid list moderation markup breaks webkit]
    * Visit https://edge.launchpad.net/~launchpad-users/+mailinglist-moderate
    * Verify the From address shows the real address in the VIAGRA messages.
    * Expand a real message (not an empty spam)
    * Verify the paragraphs are shown as paragraphs.

Bug #505919 [milestones assignees wrap poorly]
    * Visit https://edge.launchpad.net/launchpad-registry/+milestone/10.01
    * Verify assignee names do not wrap in activities portlet.

Bug #505917 [Sort milestone bugs by status by default]
    * Visit https://edge.launchpad.net/launchpad-registry/+milestone/10.01
    * Verify that the bugs are listed from triaged, in progress, fix committed
      to fix released.

Lint
----

Linting changed files:
  lib/canonical/launchpad/icing/style-3-0.css
  lib/lp/registry/browser/mailinglists.py
  lib/lp/registry/browser/milestone.py
  lib/lp/registry/browser/tests/mailinglist-message-views.txt
  lib/lp/registry/browser/tests/milestone-views.txt
  lib/lp/registry/browser/tests/test_views.py
  lib/lp/registry/templates/milestone-index.pt

Test
----

    * lib/lp/registry/browser/tests/test_views.py
      * Registered mailinglist-message-views.tx...

Read more...

Revision history for this message
Brad Crittenden (bac) wrote :

Hi Curtis,

Thanks for these changes. Everything looks really good. I would suggest you put a test into _append_paragraph to ensure current_paragraph is not empty. This could happen if you closed the paragraph right before you hit line 20. It's not big deal but we shouldn't generate an empty <p></p> if we can easily help it.

review: Approve (code)
Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi Brad.

On Tue, 2010-01-12 at 14:18 +0000, Brad Crittenden wrote:
> Review: Approve code
> Hi Curtis,
>
> Thanks for these changes. Everything looks really good. I would
> suggest you put a test into _append_paragraph to ensure
> current_paragraph is not empty. This could happen if you closed the
> paragraph right before you hit line 20. It's not big deal but we
> shouldn't generate an empty <p></p> if we can easily help it.

Thanks for your insight. I discounted the end case because leading and
trailing white space are stripped before the paragraphs are formatted.
But putting the rule in the helper method makes the rule clear and
universal. So I moved it.

=== modified file 'lib/lp/registry/browser/mailinglists.py'
--- lib/lp/registry/browser/mailinglists.py 2010-01-09 16:48:38 +0000
+++ lib/lp/registry/browser/mailinglists.py 2010-01-12 15:56:02 +0000
@@ -158,7 +158,7 @@
         for lineno, line in enumerate(details.splitlines()):
             if lineno > 20:
                 break
- if len(line.strip()) == 0 and len(current_paragraph) > 0:
+ if len(line.strip()) == 0:
                 self._append_paragraph(paragraphs, current_paragraph)
                 current_paragraph = []
             else:
@@ -167,6 +167,10 @@
         self.body_details = u''.join(paragraphs)

     def _append_paragraph(self, paragraphs, current_paragraph):
+ if len(current_paragraph) == 0:
+ # There is nothing to append. The message has multiple
+ # blank lines.
+ return
         paragraphs.append(u'\n<p>\n')
         paragraphs.append(u'\n'.join(current_paragraph))
         paragraphs.append(u'\n</p>\n')

--
__Curtis C. Hovey_________
http://launchpad.net/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
--- lib/canonical/launchpad/icing/style-3-0.css 2010-01-06 02:02:18 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css 2010-01-13 14:18:16 +0000
@@ -276,6 +276,9 @@
276 display: inline;276 display: inline;
277 padding: 0 1.5em 0 0;277 padding: 0 1.5em 0 0;
278 }278 }
279.pre {
280 white-space: pre;
281 }
279.table-actions:nth-child(2) {282.table-actions:nth-child(2) {
280 /* text-align only works here because the <li> also has the283 /* text-align only works here because the <li> also has the
281 style display:inline.284 style display:inline.
282285
=== modified file 'lib/lp/registry/browser/mailinglists.py'
--- lib/lp/registry/browser/mailinglists.py 2009-08-16 17:21:54 +0000
+++ lib/lp/registry/browser/mailinglists.py 2010-01-13 14:18:16 +0000
@@ -130,7 +130,8 @@
130 # The author field is very close to what the details has, except that130 # The author field is very close to what the details has, except that
131 # the view wants to include a link to the person's overview page.131 # the view wants to include a link to the person's overview page.
132 self.author = '<a href="%s">%s</a>' % (132 self.author = '<a href="%s">%s</a>' % (
133 canonical_url(self.details.author), self.details.sender)133 canonical_url(self.details.author),
134 escape(self.details.sender))
134135
135 def initialize(self):136 def initialize(self):
136 """See `LaunchpadView`."""137 """See `LaunchpadView`."""
@@ -158,13 +159,21 @@
158 if lineno > 20:159 if lineno > 20:
159 break160 break
160 if len(line.strip()) == 0:161 if len(line.strip()) == 0:
161 paragraphs.append(u'\n'.join(current_paragraph))162 self._append_paragraph(paragraphs, current_paragraph)
162 paragraphs.append('\n<p>\n')
163 current_paragraph = []163 current_paragraph = []
164 else:164 else:
165 current_paragraph.append(line)165 current_paragraph.append(line)
166 self._append_paragraph(paragraphs, current_paragraph)
167 self.body_details = u''.join(paragraphs)
168
169 def _append_paragraph(self, paragraphs, current_paragraph):
170 if len(current_paragraph) == 0:
171 # There is nothing to append. The message has multiple
172 # blank lines.
173 return
174 paragraphs.append(u'\n<p>\n')
166 paragraphs.append(u'\n'.join(current_paragraph))175 paragraphs.append(u'\n'.join(current_paragraph))
167 self.body_details = u''.join(paragraphs)176 paragraphs.append(u'\n</p>\n')
168177
169 def _remove_leading_blank_lines(self):178 def _remove_leading_blank_lines(self):
170 """Strip off any leading blank lines.179 """Strip off any leading blank lines.
171180
=== modified file 'lib/lp/registry/browser/milestone.py'
--- lib/lp/registry/browser/milestone.py 2009-12-05 18:37:28 +0000
+++ lib/lp/registry/browser/milestone.py 2010-01-13 14:18:16 +0000
@@ -214,7 +214,7 @@
214 """The list of non-conjoined bugtasks targeted to this milestone."""214 """The list of non-conjoined bugtasks targeted to this milestone."""
215 user = getUtility(ILaunchBag).user215 user = getUtility(ILaunchBag).user
216 params = BugTaskSearchParams(user, milestone=self.context,216 params = BugTaskSearchParams(user, milestone=self.context,
217 orderby=['-importance', 'datecreated', 'id'],217 orderby=['status', '-importance', 'id'],
218 omit_dupes=True)218 omit_dupes=True)
219 tasks = getUtility(IBugTaskSet).search(params)219 tasks = getUtility(IBugTaskSet).search(params)
220 # We could replace all the code below with a simple220 # We could replace all the code below with a simple
221221
=== added file 'lib/lp/registry/browser/tests/mailinglist-message-views.txt'
--- lib/lp/registry/browser/tests/mailinglist-message-views.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/mailinglist-message-views.txt 2010-01-13 14:18:16 +0000
@@ -0,0 +1,74 @@
1Held message view
2-----------------
3
4The HeldMessageView formats a held message so that it can be moderated by
5the mailing list administrator.
6
7 >>> from canonical.launchpad.interfaces import IMessageSet
8
9 >>> team, mlist = factory.makeTeamAndMailingList('parrot', 'owner')
10
11 >>> login('admin@canonical.com')
12 >>> message_set = getUtility(IMessageSet)
13 >>> message = message_set.fromEmail("""\
14 ... From: Carlos <carlos@canonical.com>
15 ... To: parrot@lists.launchpad.dev
16 ... Subject: monkey
17 ... Message-ID: <monkey>
18 ... Date: Fri, 01 Aug 2000 01:09:00 -0000
19 ...
20 ... First paragraph.
21 ...
22 ... Second paragraph.
23 ...
24 ... Third paragraph.
25 ... """)
26 >>> held_message = mlist.holdMessage(message)
27 >>> transaction.commit()
28
29 >>> login_person(team.teamowner)
30 >>> view = create_initialized_view(
31 ... held_message, name='+moderation', principal=team.teamowner)
32 >>> print view.widget_name
33 field.%3Cmonkey%3E
34 >>> print view.message_id
35 <monkey>
36 >>> print view.subject
37 monkey
38 >>> print view.date
39 2000-08-01 01:09:00+00:00
40 >>> print view.author
41 <a href="http://launchpad.dev/~carlos">Carlos &lt;carlos@canonical.com&gt;</a>
42 >>> print view.body_summary
43 First paragraph.
44 >>> print view.body_details
45 <p>
46 Second paragraph.
47 </p>
48 <p>
49 Third paragraph.
50 </p>
51
52
53View helpers
54------------
55
56The view uses a helper method to format the paragraphs.
57
58 >>> paragraphs = []
59 >>> current_paragraph = ['line 1', 'line 2']
60 >>> view._append_paragraph(paragraphs, current_paragraph)
61 >>> for line in paragraphs:
62 ... print line
63 <p>
64 line 1
65 line 2
66 </p>
67
68The _append_paragraph method ignores empty paragraphs.
69
70 >>> paragraphs = []
71 >>> current_paragraph = []
72 >>> view._append_paragraph(paragraphs, current_paragraph)
73 >>> len(paragraphs)
74 0
075
=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
--- lib/lp/registry/browser/tests/milestone-views.txt 2009-12-02 15:18:08 +0000
+++ lib/lp/registry/browser/tests/milestone-views.txt 2010-01-13 14:18:16 +0000
@@ -72,11 +72,14 @@
72The has_bugs_or_specs boolean property can be used to verify if the72The has_bugs_or_specs boolean property can be used to verify if the
73milestone has any bugs or specifications.73milestone has any bugs or specifications.
7474
75 >>> from lp.bugs.interfaces.bugtask import BugTaskStatus
76
75 >>> view.has_bugs_or_specs77 >>> view.has_bugs_or_specs
76 False78 False
7779
78 >>> bug = factory.makeBug(title="kiwi")80 >>> bug = factory.makeBug(title="kiwi")
79 >>> bugtask = factory.makeBugTask(bug=bug, target=milestone.product)81 >>> bugtask = factory.makeBugTask(bug=bug, target=milestone.product)
82 >>> bugtask.transitionToStatus(BugTaskStatus.FIXCOMMITTED, person)
80 >>> bugtask.transitionToAssignee(person)83 >>> bugtask.transitionToAssignee(person)
81 >>> bugtask.milestone = milestone84 >>> bugtask.milestone = milestone
82 >>> spec = factory.makeSpecification(85 >>> spec = factory.makeSpecification(
@@ -142,6 +145,7 @@
142145
143 >>> bug = factory.makeBug(title="emo")146 >>> bug = factory.makeBug(title="emo")
144 >>> bugtask = factory.makeBugTask(bug=bug, target=milestone.product)147 >>> bugtask = factory.makeBugTask(bug=bug, target=milestone.product)
148 >>> bugtask.transitionToAssignee(person)
145 >>> bugtask.milestone = milestone149 >>> bugtask.milestone = milestone
146 >>> spec = factory.makeSpecification(150 >>> spec = factory.makeSpecification(
147 ... product=milestone.product, title='ostrich')151 ... product=milestone.product, title='ostrich')
@@ -154,6 +158,15 @@
154 >>> print view.specification_count_text158 >>> print view.specification_count_text
155 <strong>2 blueprints</strong>159 <strong>2 blueprints</strong>
156160
161Bugtasks are ordered by status (fix released last), and importance
162(critical first).
163
164 >>> for bugtask in view.bugtasks:
165 ... assignee = bugtask.assignee
166 ... print bugtask.bug.title, assignee.name, bugtask.status.title
167 emo puffin-owner New
168 kiwi puffin-owner Fix Committed
169
157The view provides a list of StatusCounts that summarise the targeted170The view provides a list of StatusCounts that summarise the targeted
158specifications and bugtasks.171specifications and bugtasks.
159172
@@ -174,7 +187,8 @@
174187
175 >>> for status_count in view.bugtask_status_counts:188 >>> for status_count in view.bugtask_status_counts:
176 ... print '%s: %s' % (status_count.status.title, status_count.count)189 ... print '%s: %s' % (status_count.status.title, status_count.count)
177 New: 2190 New: 1
191 Fix Committed: 1
178192
179The assignment_counts property returns all the users and count of bugs and193The assignment_counts property returns all the users and count of bugs and
180specifications assigned to them.194specifications assigned to them.
@@ -711,4 +725,3 @@
711 >>> view = create_initialized_view(milestone, '+delete')725 >>> view = create_initialized_view(milestone, '+delete')
712 >>> check_permission('launchpad.Edit', view)726 >>> check_permission('launchpad.Edit', view)
713 False727 False
714
715728
=== modified file 'lib/lp/registry/browser/tests/test_views.py'
--- lib/lp/registry/browser/tests/test_views.py 2009-08-18 02:00:17 +0000
+++ lib/lp/registry/browser/tests/test_views.py 2010-01-13 14:18:16 +0000
@@ -21,6 +21,7 @@
21# that require something special like the librarian or mailman must run21# that require something special like the librarian or mailman must run
22# on a layer that sets those services up.22# on a layer that sets those services up.
23special_test_layer = {23special_test_layer = {
24 'mailinglist-message-views.txt': LaunchpadFunctionalLayer,
24 'milestone-views.txt': LaunchpadFunctionalLayer,25 'milestone-views.txt': LaunchpadFunctionalLayer,
25 'person-views.txt': LaunchpadFunctionalLayer,26 'person-views.txt': LaunchpadFunctionalLayer,
26 'user-to-user-views.txt': LaunchpadFunctionalLayer,27 'user-to-user-views.txt': LaunchpadFunctionalLayer,
2728
=== modified file 'lib/lp/registry/doc/message-holds.txt'
--- lib/lp/registry/doc/message-holds.txt 2009-05-12 21:39:35 +0000
+++ lib/lp/registry/doc/message-holds.txt 2010-01-13 14:18:16 +0000
@@ -377,58 +377,6 @@
377 ----------------------------------------377 ----------------------------------------
378378
379379
380== Held message formatting ==
381
382When held messages are displayed in the u/i, they will be formatted in such a
383way that we can show the body summary with an ellipsis to review more body
384details. Because formatting email messages for the web can get tricky, we
385take a fairly conservative 80/20 approach.
386
387 >>> from lp.registry.browser.mailinglists import HeldMessageView
388 >>> from canonical.launchpad.interfaces import (
389 ... IHeldMessageDetails, IPersonSet)
390 >>> from zope.component import getUtility
391 >>> from zope.interface import implements
392
393First, we craft an object like what the view expects.
394
395 >>> person = getUtility(IPersonSet).getByName('name12')
396 >>> class FakeHeldMessageDetails:
397 ... implements(IHeldMessageDetails)
398 ... message_id = '<zebra>'
399 ... subject = 'A subject'
400 ... date = 'Today'
401 ... author = person
402 ... sender = 'test@example.com'
403 ... body = """\
404 ... What are the Guadamen? I think I might want to be come one
405 ... but I'm not sure. Are they men who like guacamole? Or are
406 ... they men who live on Gauadalcanal? I used to live on Guadalcanal
407 ... and boy, do I sure like avocados!
408 ...
409 ... Please consider me for Guadamanhood.
410 ...
411 ... -Guy
412 ... """
413
414 >>> view = HeldMessageView(FakeHeldMessageDetails(), None)
415 >>> view.initialize()
416 >>> view.widget_name
417 'field.%3Czebra%3E'
418 >>> view.author
419 u'<a href="http://launchpad.dev/~name12">test@example.com</a>'
420 >>> view.body_summary
421 'What are the Guadamen? I think I might want to be come one'
422 >>> print view.body_details
423 but I'm not sure. Are they men who like guacamole? Or are
424 they men who live on Gauadalcanal? I used to live on Guadalcanal
425 and boy, do I sure like avocados!
426 <p>
427 Please consider me for Guadamanhood.
428 <p>
429 -Guy
430
431
432== Posting privileges ==380== Posting privileges ==
433381
434Message approvals are also used to derived posting privileges for non-team382Message approvals are also used to derived posting privileges for non-team
435383
=== modified file 'lib/lp/registry/templates/milestone-index.pt'
--- lib/lp/registry/templates/milestone-index.pt 2009-12-11 19:54:04 +0000
+++ lib/lp/registry/templates/milestone-index.pt 2010-01-13 14:18:16 +0000
@@ -147,11 +147,10 @@
147 <dl>147 <dl>
148 <dt>Assignees:</dt>148 <dt>Assignees:</dt>
149 <dd tal:define="count_statuses view/assignment_counts">149 <dd tal:define="count_statuses view/assignment_counts">
150 <tal:statuses repeat="count_status count_statuses">150 <span class="pre"
151 <strong tal:content="count_status/count">2</strong>&nbsp;<a151 tal:repeat="count_status count_statuses"><strong tal:content="count_status/count">2</strong> <a
152 tal:replace="structure count_status/status/fmt:link" /><tal:comma152 tal:replace="structure count_status/status/fmt:link" /><tal:comma
153 condition="not: repeat/count_status/end">,</tal:comma>153 condition="not: repeat/count_status/end">,</tal:comma></span>
154 </tal:statuses>
155 <tal:no-statuses condition="not: count_statuses">154 <tal:no-statuses condition="not: count_statuses">
156 No users assigned to blueprints and bugs.155 No users assigned to blueprints and bugs.
157 </tal:no-statuses>156 </tal:no-statuses>