Merge lp:~brian-murray/launchpad/limited-subscriptions-page into lp:launchpad

Proposed by Brian Murray
Status: Merged
Approved by: Brian Murray
Approved revision: no longer in the source branch.
Merged at revision: 11669
Proposed branch: lp:~brian-murray/launchpad/limited-subscriptions-page
Merge into: lp:launchpad
Diff against target: 587 lines (+343/-20)
11 files modified
lib/canonical/launchpad/webapp/launchpadform.py (+1/-1)
lib/lp/bugs/browser/bugtask.py (+35/-4)
lib/lp/bugs/stories/bug-privacy/40-unsubscribe-from-private-bug.txt (+1/-1)
lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions.txt (+9/-8)
lib/lp/bugs/templates/bug-subscription.pt (+5/-2)
lib/lp/registry/browser/configure.zcml (+6/-0)
lib/lp/registry/browser/person.py (+49/-1)
lib/lp/registry/browser/tests/test_person_view.py (+32/-2)
lib/lp/registry/stories/person/xx-person-subscriptions.txt (+131/-0)
lib/lp/registry/stories/productseries/xx-productseries-delete.txt (+1/-1)
lib/lp/registry/templates/person-subscriptions.pt (+73/-0)
To merge this branch: bzr merge lp:~brian-murray/launchpad/limited-subscriptions-page
Reviewer Review Type Date Requested Status
Curtis Hovey (community) ui Approve
Guilherme Salgado (community) ui* Approve
Graham Binns (community) code Approve
Review via email: mp+35177@code.launchpad.net

Commit message

Add in the start of a +subscriptions page for managing subscriptions in Launchpad. Currently contains direct bug subscriptions.

Description of the change

= Summary =

This branch is the start of a +subscriptions page for people and teams that will display information about all items that people are subscribed to in Launchpad and allow them to unsubscribe from those items.

As this is the first revision only direct bug subscriptions have been included and the page is not yet linked to.

== Pre-implementation notes ==

Deryck and I talked about this and decided that it would be worthwhile to show duplicate bugs as these can be harder for people to unsubscribe from these. I've also decided to include Invalid as these can receive noisy traffic.

== Implementation details ==

lib/lp/registry/browser/configure.zcml
    add in +subscriptions page

lib/lp/registry/browser/person.py
    add in PersonSubscriptionsView class
    subscribedBugTasks() returns only one task per bug to prevent duplicates showing up as you can only unsubscribe from bugs not bug tasks
    fix a typo in getSimpleSearchURL()

lib/lp/registry/stories/person/xx-person-subscriptions.txt
    created a new test for the +subscriptions page

lib/lp/registry/templates/person-subscriptions.pt
    created the +subscriptions page

== Tests ==

bin/test -cvvt xx-person-subscriptions.txt

== Demo and QA ==

Login in as sample person and go to http://launchpad.dev/~name12/+subscriptions also check http://launchpad.dev/~ubuntu-team/+subscriptions for a person with no bug subscriptions.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (4.5 KiB)

Hi Brian,

I like this branch; nice work. Couple of minor changes needed, but
nothing that isn't just nitpicking. I've also posed a question about
performance, but I'm not sure that we can do much about at this point.

You should get a UI review for this branch from one of our sainted UI
reviewers before you land it.

> === modified file 'lib/lp/registry/browser/person.py'
> --- lib/lp/registry/browser/person.py 2010-09-10 12:24:03 +0000
> +++ lib/lp/registry/browser/person.py 2010-09-10 22:30:12 +0000
> @@ -2396,6 +2397,42 @@
> return self.getSearchPageHeading()
>
>
> +class PersonSubscriptionsView(BugTaskSearchListingView):
> + """All the subscriptions for a person."""
> +
> + page_title = 'Subscriptions'
> +
> + def subscribedBugTasks(self):
> + """Return a BatchNavigator for distinct bug tasks to which the
> + person is subscribed."""
> + bugtasks = self.context.searchTasks(None, user=self.user,

bugtasks => bug_tasks.

> + order_by='-date_last_updated',
> + status=(BugTaskStatus.NEW,
> + BugTaskStatus.INCOMPLETE,
> + BugTaskStatus.CONFIRMED,
> + BugTaskStatus.TRIAGED,
> + BugTaskStatus.INPROGRESS,
> + BugTaskStatus.FIXCOMMITTED,
> + BugTaskStatus.FIXRELEASED))

You can outdent all of this by four spaces; we use four-space indents
per level in method calls.

> +
> + sub_bugtasks = []
> + sub_bugs = []
> +
> + for task in bugtasks:
> + if task.bug not in sub_bugs:
> + sub_bugtasks.append(task)
> + sub_bugs.append(task.bug)

Is this loop going to cause performance problems for people with a large
number of direct subscriptions?

> + return BatchNavigator(sub_bugtasks, self.request)
> +
> + def getSubscriptionsPageHeading(self):
> + """The header for the subscriptions page."""
> + return "Subscriptions for %s" % self.context.displayname
> +
> + @property
> + def label(self):
> + return self.getSubscriptionsPageHeading()
> +
> +
> class PersonVouchersView(LaunchpadFormView):
> """Form for displaying and redeeming commercial subscription vouchers."""
>
> === added file 'lib/lp/registry/stories/person/xx-person-subscriptions.txt'
> --- lib/lp/registry/stories/person/xx-person-subscriptions.txt 1970-01-01 00:00:00 +0000
> +++ lib/lp/registry/stories/person/xx-person-subscriptions.txt 2010-09-10 22:30:12 +0000
> @@ -0,0 +1,56 @@
> +Subscriptions View
> +==================
> +
> +Direct bug subscriptions
> +
> +Each person and team has a subscriptions page that lists things to which they
> +are directly subscribed.
> +
> + >>> anon_browser.open('http://launchpad.dev/~ubuntu-team/+subscriptions')
> + >>> print anon_browser.title
> + Subscriptions : “Ubuntu Team” team
> +
> +The bug subscriptions table does not appear for people without any direct bug
> +subscriptions.
> +
> + >>> page_text = extract_text(find_main_content(anon_browser.contents))
> + >>> "does not have any direct bug subscriptions" in page_text
> + Tru...

Read more...

review: Approve (code)
Revision history for this message
Guilherme Salgado (salgado) wrote :

Hi Brian,

It's great to see that work is being done on this long-awaited feature, but I was expecting to see some mockups for what we expect this page to look like and how it'd work. Something similar to what the Soyuz team is doing with derivative distributions. Do you know if there's a LEP for this? Or maybe just some mockups? I think it's important to work on those (maybe with help from the Design team, as described in https://dev.launchpad.net/UI/Design) for this feature, as the current approach may not scale well once we start adding the other types of items that a person can subscribe to this page.

Regardless of that, here's some feedback on the current implementation.

In the long term I'm sure we want to use ajax to unsubscribe without leaving the page, and given how easy it is not much more work to do so, I think it'd make sense to do that from the start. That would probably make an undo functionality more important, but I think we can live without that for now as that'd complicate things a bit more.

Also, upon clicking the unsubscribe link, you're taken to the bug's +subscribe page, and clicking Cancel there will take you to the bug's +index page rather than to +subscriptions as one would expect. This wouldn't be a problem if we were using ajax.

The page should not include the unsubscribe buttons when looking at somebody else's page, as the only thing you can do is unsubscribe yourself but not the person whose page you're looking at. Either that or make the page not available for other users.

For teams, the unsubscribe link takes you to the bug's +subscribe page but if you're not currently subscribed to that bug, the default option (and the heading) is for you to subscribe yourself rather than to unsubscribe the team. When you are subscribed, the default options is to unsubscribe yourself. This is a bit confusing, but we could avoid this completely by using ajax to unsubscribe directly from +subscriptions.

review: Abstain (ui*)
Revision history for this message
Curtis Hovey (sinzui) wrote :

I think Salgado is review of the UI is very good. This is a much needed feature, but it introduces a lot of UI confusion. This feature needs a feature flag to ensure lpnet users do not see this until it is ready.

This UI will get complicated in the next year because we do need to move blueprint and answers subscriptions into this page. I think AJAX is going to be a requirement by then.

I do not think this can land until some of the obvious UI confusion is addressed.

You can use this mixin so that the view returns the user to the correct page:
    from canonical.launchpad.webapp.launchpadform import ReturnToReferrerMixin
But I think switching to AJAX will be the only way to address the clarity of the operation that the user needs to accomplish--users can be in 50 teams and that will distract the user from his task.

Do not show me remove icon when the page is an edit operations (switching to AJAX will fix this).

Do not show me edit operations for other users and teams I am not a member of.

The story is not a story. It is not written from the perspective of a user who visits Lp to accomplish a task. I expect to see the user arrive at his profile page, choose the link to +subscriptions, then use a remove link to remove the subscription, then he sees the bug is not listed. I suppose we want a similar story for removing a subscription for a user's team. The conditions for not showing the bug listing belong in a test for the view

I have read the story and I have no idea how I will discover the link to this page. I assume we are not publishing the link in pages to prevent users from seeing the link until we are ready. If this is the case you need an XXX in the story explaining why the user does not start on the page that directs the user to look at his subscriptions. The other course is to use a feature flag that hides the link and the page until we are ready.

review: Needs Fixing (ui)
Revision history for this message
Brian Murray (brian-murray) wrote :

This is the first branch I've done requiring UI review or that is fulfilling a LEP. The LEP is located at - https://dev.launchpad.net/Research/Bugs/Subscriptions2010.

Revision history for this message
Guilherme Salgado (salgado) wrote :

That looks more like a report from user testing sessions than a LEP. I
was expecting something that would clearly say how the UI should
look/behave. Is that the only document you're using to drive the
implementation of this? Maybe it'd be worth writing a LEP first?

Revision history for this message
Deryck Hodge (deryck) wrote :
Download full text (4.2 KiB)

Hi, salgado and sinzui.

We do have a LEP for this feature. This +subscriptions page is one bit of the "Better Subscriptions and Notifications" story we're currently doing development work on. The LEP is at:

https://dev.launchpad.net/LEP/BetterBugSubscriptionsAndNotifications

This is an old LEP now, and I think we've refined the process a bit since writing this one, so maybe some elements are missing that should be there. This is not Brian's fault, so please don't block Brian for this reason. Let me know if you need something there, and I'll add it.

The link Brian provided is the result of the user research we did with mrevell for all of the UI elements within this story.

https://dev.launchpad.net/Research/Bugs/Subscriptions2010

A bit of history here may help with some of your concerns, too. We did try to work with the DUX team on this feature. Francis, Jono, Tom, Graham, and I did a fair amount of coordination work, and in the end we spent two months chasing meetings, which slowed the progress of this feature. We did get valuable feedback from a couple sessions with Alejandra about subscriptions UI elements, and this +subscriptions page is a direct result of that feedback. We also then did two rounds of user testing on these mockups with mrevell. I think we've more than covered ourselves in terms of thinking about the UI before beginning to implement it.

Having said that, I don't think https://dev.launchpad.net/UI/Design should be consider the gospel for UI procedure, at least the initial pre-imp recommendation in its current form. We on the bugs team in particular are trying to experiment with approaches to UI design to achieve quicker turnaround on both producing mockups and receiving feedback. I think a pre-imp with a UI reviewer should be considered the same as for a code reviewer -- recommended, but the designer/programmer can use his/her own discretion. We also cannot block on speaking with the DUX team as my team has already proven this will delay work beyond a reasonable amount of time. Mockups, of course, are a reasonable requirement. Regardless, in this case, we've covered all our bases. ;)

Finally, as to some of your more technical concerns. I have asked Brian to land this page without any Ajax enabled widgets initially. The subscription widget is not easily re-usable, and it would require one or two more branches to get widgets enabled for this page. This is meant to be the very first step in this process for Brian. He has many more branches to go until this work is completed -- adding Ajax widgets, adding sections for every item we support a subscription on, good paginating for these things, performance tuning every step of the way, and finally making a link available to users so they become aware of this page. I would prefer we don't add a link and hide it with a feature flag since you guys are correct that we haven't yet thought about where this link should go. I think there is value in telling a few key stakeholders about this page as we work on it, and this seems simpler to me than feature flagging it, at least at this point in the life of the feature flag system. If we do require this, let's have...

Read more...

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (5.3 KiB)

On Mon, 2010-09-20 at 13:52 +0000, Deryck Hodge wrote:
> Hi, salgado and sinzui.
>
> We do have a LEP for this feature. This +subscriptions page is one
> bit of the "Better Subscriptions and Notifications" story we're
> currently doing development work on. The LEP is at:
>
> https://dev.launchpad.net/LEP/BetterBugSubscriptionsAndNotifications
>
> This is an old LEP now, and I think we've refined the process a bit
> since writing this one, so maybe some elements are missing that should
> be there. This is not Brian's fault, so please don't block Brian for
> this reason. Let me know if you need something there, and I'll add
> it.
>
> The link Brian provided is the result of the user research we did with
> mrevell for all of the UI elements within this story.
>
> https://dev.launchpad.net/Research/Bugs/Subscriptions2010
>
> A bit of history here may help with some of your concerns, too. We
> did try to work with the DUX team on this feature. Francis, Jono,
> Tom, Graham, and I did a fair amount of coordination work, and in the
> end we spent two months chasing meetings, which slowed the progress of
> this feature. We did get valuable feedback from a couple sessions
> with Alejandra about subscriptions UI elements, and this
> +subscriptions page is a direct result of that feedback. We also then
> did two rounds of user testing on these mockups with mrevell. I think
> we've more than covered ourselves in terms of thinking about the UI
> before beginning to implement it.

Cool, that's good to know. I asked for a LEP because I'd expect it to
either contain some of this information or at show me that this UI (and
possibly others) had been thought through. By looking at the merge
proposal this was not clear.

>
> Having said that, I don't think https://dev.launchpad.net/UI/Design
> should be consider the gospel for UI procedure, at least the initial
> pre-imp recommendation in its current form. We on the bugs team in
> particular are trying to experiment with approaches to UI design to
> achieve quicker turnaround on both producing mockups and receiving
> feedback. I think a pre-imp with a UI reviewer should be considered
> the same as for a code reviewer -- recommended, but the
> designer/programmer can use his/her own discretion. We also cannot
> block on speaking with the DUX team as my team has already proven this
> will delay work beyond a reasonable amount of time. Mockups, of
> course, are a reasonable requirement. Regardless, in this case, we've
> covered all our bases. ;)

I completely agree.

Someone (I think Curtis) said that UI reviews are more about making sure
people think carefully about UI changes rather than anything else, and
that's what I was trying to do by pointing to the wiki page. Again, the
merge proposal had no indication any of this was done.

>
> Finally, as to some of your more technical concerns. I have asked
> Brian to land this page without any Ajax enabled widgets initially.
> The subscription widget is not easily re-usable, and it would require
> one or two more branches to get widgets enabled for this page. This
> is meant to be the very first step in this process for Brian. He has
> many ...

Read more...

review: Needs Fixing
Revision history for this message
Robert Collins (lifeless) wrote :

+1 on not exposing this until we're fairly comfortable with it.

You can enable it via a feature flag to permit user testing as we go:

<a tal:condition="features/bugs.subscriptions_page" href="link to the
page here" />

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

I am review r11483,

I am seeing some problems as Sample Person when I try to manage my subscriptions
    https://launchpad.dev/~name12/+subscriptions
 * I choose to remove the blackhole subscription. The link to edit it shows a radio button. I cannot unselected it. I can change it.
 * If The cancel link goes to the bug, not +subscriptions

Check my teams (as Sample Person)
    https://launchpad.dev/~hwdb-team/+subscriptions
 * I cannot unselect the radio button for the team. I can of course select myself to deselect the team, but I do not want to subscribe to the bug
 * Again the cancel link the bug, not +subscriptions

As anonymous when I see https://launchpad.dev/~hwdb-team/+subscriptions I read
    HWDB Team does not have any direct bug subscriptions.
^ that is great, I think the text for the bug listing table should make it clear I am seeing "direct bug subscriptions"

I think part of the problem here is that I am reading the story, but it is clearly not a story. A story is from a users perspective and explains his experience performing a task. There are not tasks completed in this story, and when I try to complete what I think Sample Person is doing, I fail. I will reply in about 30 minutes with a revised story I what I think the users' goals are.

review: Needs Fixing (ui)
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (5.6 KiB)

This is closer to a proper story and described the goals that Sample Person uses
+subscriptions to accomplish.

Subscriptions View
==================

XXX: bdmurray 2010-09-24 bug=628411 This story is complete until we publish
the link that leads to the +subscriptions page.

XXX: A story is written from a user's perspective. It explains how a user
accomplishes a task and how he know it. Use user names and rules when
telling the story. Stories do not test failure,
permissions, or method functions--that is the domain of unittests.

Any user can view the direct subcriptions that a person or team has to
bugs, blueprints, questions, branches, and merge proposals.

    >>> anon_browser.open('http://launchpad.dev/~ubuntu-team/+subscriptions')
    >>> print anon_browser.title
    Subscriptions : “Ubuntu Team” team

The user can see that the Ubuntu Team does not have an direct bug
subscritions.

    >>> page_text = extract_text(find_main_content(anon_browser.contents))
    >>> "does not have any direct bug subscriptions" in page_text
    True

Any user can see that that Sample Person is subscribed to several bugs.
The bug subscriptions table includes the bug number, title and location.

    >>> anon_browser.open('http://launchpad.dev/~name12/+subscriptions')
    >>> bug_sub_table = find_tag_by_id(
    ... anon_browser.contents, 'bug_subscriptions')
    >>> for tr in bug_sub_table.findAll('tr'):
    ... print extract_text(tr)
    Summary
    In
    13
    Launchpad CSS and JS is not testible
    Launchpad
    5
    Firefox install instructions should be complete
    ...
    4
    Reflow problems with complex page layouts
    Mozilla Firefox
    2
    Blackhole Trash folder
    ...
    9
    Thunderbird crashes
    thunderbird (Ubuntu)

Sample Person can see his see his direct bug subscriptions too, and he can
use the page manage his direct subscriptions. He chooses to view
bug 13, "Launchpad CSS and JS is not testible".

    >>> sample_browser = setupBrowser(auth='Basic <email address hidden>:test')
    >>> sample_browser.open('http://bugs.launchpad.dev/~name12/+subscriptions')
    >>> sample_browser.getLink(id='unsubscribe-subscriber-12').click()
    >>> print sample_browser.title
   Unsubscribe from bug #13

After viewing the +subscribe page Sample Person decides that he want to
reamain subscribed to bug 13. He uses the cancel link to

    >>> sample_browser.getLink('Cancel').click()
    >>> print sample_browser.title
    Subscriptions: Sample Person

He chooses to unsubscribe from bug 2, "Blackhole Trash folder".

    >>> sample_browser.getLink(id='unsubscribe-subscriber-2').click()
    >>> print sample_browser.title
   Unsubscribe from bug #2

Sample Person unselects the checkbox and continues.

    >>> sample_browser.getControl('Unsubscribe me from this bug') = False
    >>> sample_browser.getControl('Continue').click()
    >>> print sample_browser.title
    Subscriptions: Sample Person

Sample Person can see that bug #2 is not listed in his direct bug
subscriptions.

    >>> print extract_text(find_tag_by_id(
    ... sample_browser.contents, 'bug_subscriptions'))
    Summary In
    13 Launchp...

Read more...

Revision history for this message
Brian Murray (brian-murray) wrote :

I believe I've addressed all the issues with the review of this branch and feel it is now ready for review again.

Revision history for this message
Guilherme Salgado (salgado) wrote :

Thanks for fixing the things I'd reported, Brian. The only remaining issue I see is the 'Cancel' link on +subscribe which still sends me to the bug's page, but that might be a bug on ReturnToReferrerMixin or just an oversight on your side. If it's the former, it's fine to just file a bug, but if it's the latter it should be trivial to fix.

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

Salgado, Brian BugTaskView's cancel url is not hooked up the same way that next_url is. I think this is a simple property, or it may be cancel_url = next_url in the view.

Thanks for making the changes Brian. I think this is good to land.

review: Approve (ui)
Revision history for this message
Guilherme Salgado (salgado) wrote :

The Continue button shows the same behaviour for me, but what is weird is that they work as we expect in the test.

I couldn't figure out what is going on, but this might be related to the fact that ReturnToReferrerMixin must be mixed into a LaunchpadFormView, but here it was mixed into a LaunchpadView. That mixin works by storing a hidden field (_return_url) in the page's form and mapping that field to a view property with the same name. Here the hidden field is not stored automatically by the mixin as the page does not use launchpad-form.pt (since it's not a LaunchpadFormView), so the hidden field is created manually. The hidden field created manually has a different name, though ('next_url'), so the mixin's _return_url property would always return None. That's consistent with the behaviour I see on the two browsers I've tested (chromium and epiphany), but is not consistent with the behaviour shown by the tests.

Revision history for this message
Guilherme Salgado (salgado) wrote :

There's one thing which I overlooked in my analysis above: ReturnToReferrerMixin._return_url will return self.request.getHeader('referer') when a _return_url is not present in the request. So there must be something that is stripping referrers out of my requests. I'll figure it out; sorry for the false alarm.

Revision history for this message
Guilherme Salgado (salgado) wrote :

Damn it, found what's going on!

The test passes because it accesses the +subscription page on the bugs vhost. It works for me if I use that vhost as well.

If you use the main vhost, it won't work. I think this is a problem because this page will mix content from multiple apps, so it should probably be accessible on the main vhost.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
2--- lib/canonical/launchpad/webapp/launchpadform.py 2010-08-20 20:31:18 +0000
3+++ lib/canonical/launchpad/webapp/launchpadform.py 2010-09-30 22:08:08 +0000
4@@ -460,7 +460,7 @@
5 between the request to view the form and submitting the form.
6
7 _return_attribute_name and _return_attribute_values are also stored
8- as hidden fields and they are use to check the validity of _return_url.
9+ as hidden fields and they are used to check the validity of _return_url.
10
11 If _return_url depends on _return_attribute_name, the result of a form
12 submission can invalidate it.
13
14=== modified file 'lib/lp/bugs/browser/bugtask.py'
15--- lib/lp/bugs/browser/bugtask.py 2010-09-28 14:50:19 +0000
16+++ lib/lp/bugs/browser/bugtask.py 2010-09-30 22:08:08 +0000
17@@ -162,6 +162,7 @@
18 from canonical.launchpad.webapp.batching import TableBatchNavigator
19 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
20 from canonical.launchpad.webapp.interfaces import ILaunchBag
21+from canonical.launchpad.webapp.launchpadform import ReturnToReferrerMixin
22 from canonical.launchpad.webapp.menu import structured
23 from lp.app.browser.tales import (
24 FormattersAPI,
25@@ -620,7 +621,8 @@
26 return view.render()
27
28
29-class BugTaskView(LaunchpadView, BugViewMixin, CanBeMentoredView, FeedsMixin):
30+class BugTaskView(LaunchpadView, BugViewMixin, CanBeMentoredView,
31+ FeedsMixin):
32 """View class for presenting information about an `IBugTask`."""
33
34 override_title_breadcrumbs = True
35@@ -647,6 +649,32 @@
36 bugtask.bug.id, bugtask.bugtargetdisplayname)
37 return smartquote('%s: "%s"') % (heading, self.context.bug.title)
38
39+ @property
40+ def next_url(self):
41+ """Provided so returning to the page they came from works."""
42+ referer = self.request.getHeader('referer')
43+
44+ # XXX bdmurray 2010-09-30 bug=98437: work around zope's test
45+ # browser setting referer to localhost.
46+ if referer and referer != 'localhost':
47+ next_url = referer
48+ else:
49+ next_url = canonical_url(self.context)
50+ return next_url
51+
52+ @property
53+ def cancel_url(self):
54+ """Provided so returning to the page they came from works."""
55+ referer = self.request.getHeader('referer')
56+
57+ # XXX bdmurray 2010-09-30 bug=98437: work around zope's test
58+ # browser setting referer to localhost.
59+ if referer and referer != 'localhost':
60+ cancel_url = referer
61+ else:
62+ cancel_url = canonical_url(self.context)
63+ return cancel_url
64+
65 def initialize(self):
66 """Set up the needed widgets."""
67 bug = self.context.bug
68@@ -728,6 +756,9 @@
69 # bug page would raise Unauthorized errors!
70 if self._redirecting_to_bug_list:
71 return u''
72+ elif self._isSubscriptionRequest() and self.request.get('next_url'):
73+ self.request.response.redirect(self.request.get('next_url'))
74+ return u''
75 else:
76 return LaunchpadView.render(self)
77
78@@ -761,7 +792,7 @@
79 def _handleSubscribe(self):
80 """Handle a subscribe request."""
81 self.context.bug.subscribe(self.user, self.user)
82- self.notices.append("You have been subscribed to this bug.")
83+ self.request.response.addNotification("You have been subscribed to this bug.")
84
85 def _handleUnsubscribe(self, user):
86 """Handle an unsubscribe request."""
87@@ -833,8 +864,8 @@
88 # The user still has permission to see this bug, so no
89 # special-casing needed.
90 return (
91- "You have been unsubscribed from this bug%s." %
92- unsubed_dupes_msg_fragment)
93+ "You have been unsubscribed from bug %d%s." % (
94+ current_bug.id, unsubed_dupes_msg_fragment))
95 else:
96 return (
97 "You have been unsubscribed from bug %d%s. You no "
98
99=== modified file 'lib/lp/bugs/stories/bug-privacy/40-unsubscribe-from-private-bug.txt'
100--- lib/lp/bugs/stories/bug-privacy/40-unsubscribe-from-private-bug.txt 2010-08-22 18:31:30 +0000
101+++ lib/lp/bugs/stories/bug-privacy/40-unsubscribe-from-private-bug.txt 2010-09-30 22:08:08 +0000
102@@ -58,7 +58,7 @@
103 >>> browser.getControl("Continue").click()
104
105 >>> browser.url
106- 'http://launchpad.dev/ubuntu/+source/evolution/+bug/...'
107+ 'http://bugs.launchpad.dev/ubuntu/+source/evolution/+bug/...'
108 >>> "You have been unsubscribed" in browser.contents
109 True
110
111
112=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions.txt'
113--- lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions.txt 2010-09-09 21:00:54 +0000
114+++ lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions.txt 2010-09-30 22:08:08 +0000
115@@ -28,7 +28,7 @@
116 >>> submit.click()
117
118 >>> browser.url
119- 'http://bugs.launchpad.dev/firefox/+bug/1/'
120+ 'http://bugs.launchpad.dev/firefox/+bug/1'
121
122 >>> for tag in find_tags_by_class(browser.contents, "informational message"):
123 ... print tag.renderContents()
124@@ -72,7 +72,7 @@
125
126 >>> for tag in find_tags_by_class(browser.contents, 'informational message'):
127 ... print tag.renderContents()
128- You have been unsubscribed from this bug.
129+ You have been unsubscribed from bug 1.
130
131 Users can unsubscribe teams to which they belong. Let's demonstrate by
132 first subscribing one of Foo Bar's teams.
133@@ -120,7 +120,7 @@
134 >>> browser.getControl('Continue').click()
135
136 >>> browser.url
137- 'http://bugs.launchpad.dev/firefox/+bug/1/'
138+ 'http://bugs.launchpad.dev/firefox/+bug/1'
139
140 >>> for tag in find_tags_by_class(browser.contents, 'informational message'):
141 ... print tag.renderContents()
142@@ -146,7 +146,7 @@
143 ['name16']
144 >>> browser.getLink('Cancel').click()
145 >>> browser.url
146- 'http://bugs.launchpad.dev/firefox/+bug/1'
147+ 'http://bugs.launchpad.dev/firefox/+bug/1/'
148
149 Foo Bar wasn't subscribed to the bug.
150
151@@ -227,7 +227,7 @@
152 >>> for tag in find_tags_by_class(
153 ... stevea_browser.contents, 'informational message'):
154 ... print tag.renderContents()
155- You have been unsubscribed from this bug and 1 duplicate (<a...#2</a>)...
156+ You have been unsubscribed from bug 3 and 1 duplicate (<a...#2</a>)...
157
158 (Except for Mark, who has a structural subscription to the target,
159 there are no longer any indirect subscribers, because Steve was
160@@ -258,8 +258,9 @@
161 previous example.)
162
163 >>> stevea_browser.open(
164- ... "http://launchpad.dev/tomcat/+bug/2/+subscribe")
165- >>> stevea_browser.getControl("Continue").click()
166+ ... "http://launchpad.dev/tomcat/+bug/2/+addsubscriber")
167+ >>> stevea_browser.getControl('Person').value = 'stevea'
168+ >>> stevea_browser.getControl('Subscribe user').click()
169
170 >>> stevea_browser.open(
171 ... "http://launchpad.dev/bugs/3/+bug-portlet-dupe-subscribers-content")
172@@ -282,7 +283,7 @@
173 >>> for tag in find_tags_by_class(
174 ... stevea_browser.contents, 'informational message'):
175 ... print tag.renderContents()
176- You have been unsubscribed from this bug and 2 duplicates (<a...#1</a>, <a...#2</a>)...
177+ You have been unsubscribed from bug 3 and 2 duplicates (<a...#1</a>, <a...#2</a>)...
178
179 (Let's undupe bug #1 from bug #3, since it's unneeded for the examples
180 that follow.)
181
182=== modified file 'lib/lp/bugs/templates/bug-subscription.pt'
183--- lib/lp/bugs/templates/bug-subscription.pt 2009-09-03 08:54:21 +0000
184+++ lib/lp/bugs/templates/bug-subscription.pt 2010-09-30 22:08:08 +0000
185@@ -37,7 +37,6 @@
186 <form action="." method="POST">
187
188 <div class="field">
189-
190 <div>
191 <div tal:content="structure view/subscription_widget">
192 <input type="radio" />
193@@ -47,6 +46,9 @@
194 </div>
195
196 <div class="actions">
197+ <input type="hidden" name="next_url"
198+ tal:condition="view/next_url | nothing"
199+ tal:attributes="value view/next_url" />
200 <input tal:condition="not: view/userIsSubscribed"
201 type="submit"
202 name="subscribe"
203@@ -55,7 +57,8 @@
204 type="submit"
205 name="unsubscribe"
206 value="Continue" />
207- or <a tal:attributes="href context/fmt:url">Cancel</a>
208+ or <a tal:condition="view/cancel_url | nothing"
209+ tal:attributes="href view/cancel_url">Cancel</a>
210 </div>
211 </form>
212
213
214=== modified file 'lib/lp/registry/browser/configure.zcml'
215--- lib/lp/registry/browser/configure.zcml 2010-09-30 01:05:01 +0000
216+++ lib/lp/registry/browser/configure.zcml 2010-09-30 22:08:08 +0000
217@@ -1054,6 +1054,12 @@
218 name="+teamhierarchy"
219 template="../templates/person-teamhierarchy.pt"/>
220 <browser:page
221+ for="lp.registry.interfaces.person.IPerson"
222+ permission="zope.Public"
223+ class="lp.registry.browser.person.PersonSubscriptionsView"
224+ name="+subscriptions"
225+ template="../templates/person-subscriptions.pt"/>
226+ <browser:page
227 for="lp.registry.interfaces.person.ITeam"
228 permission="zope.Public"
229 class="lp.registry.browser.person.TeamIndexView"
230
231=== modified file 'lib/lp/registry/browser/person.py'
232--- lib/lp/registry/browser/person.py 2010-09-27 14:19:29 +0000
233+++ lib/lp/registry/browser/person.py 2010-09-30 22:08:08 +0000
234@@ -53,6 +53,7 @@
235 'PersonSpecWorkloadView',
236 'PersonSpecsMenu',
237 'PersonSubscribedBugTaskSearchListingView',
238+ 'PersonSubscriptionsView',
239 'PersonView',
240 'PersonVouchersView',
241 'RedirectToEditLanguagesView',
242@@ -2189,7 +2190,7 @@
243 return "Search bugs assigned to %s" % self.context.displayname
244
245 def getSimpleSearchURL(self):
246- """Return a URL that can be usedas an href to the simple search."""
247+ """Return a URL that can be used as an href to the simple search."""
248 return canonical_url(self.context, view_name="+assignedbugs")
249
250 @property
251@@ -2343,6 +2344,53 @@
252 return self.getSearchPageHeading()
253
254
255+class PersonSubscriptionsView(BugTaskSearchListingView):
256+ """All the subscriptions for a person."""
257+
258+ page_title = 'Subscriptions'
259+
260+ def subscribedBugTasks(self):
261+ """Return a BatchNavigator for distinct bug tasks to which the
262+ person is subscribed."""
263+ bug_tasks = self.context.searchTasks(None, user=self.user,
264+ order_by='-date_last_updated',
265+ status=(BugTaskStatus.NEW,
266+ BugTaskStatus.INCOMPLETE,
267+ BugTaskStatus.CONFIRMED,
268+ BugTaskStatus.TRIAGED,
269+ BugTaskStatus.INPROGRESS,
270+ BugTaskStatus.FIXCOMMITTED,
271+ BugTaskStatus.INVALID),
272+ bug_subscriber=self.context)
273+
274+ sub_bug_tasks = []
275+ sub_bugs = []
276+
277+ for task in bug_tasks:
278+ if task.bug not in sub_bugs:
279+ sub_bug_tasks.append(task)
280+ sub_bugs.append(task.bug)
281+ return BatchNavigator(sub_bug_tasks, self.request)
282+
283+ def canUnsubscribeFromBugTasks(self):
284+ viewed_person = self.context
285+ if self.user is None:
286+ return False
287+ elif viewed_person == self.user:
288+ return True
289+ elif (viewed_person.is_team and
290+ self.user.inTeam(viewed_person)):
291+ return True
292+
293+ def getSubscriptionsPageHeading(self):
294+ """The header for the subscriptions page."""
295+ return "Subscriptions for %s" % self.context.displayname
296+
297+ @property
298+ def label(self):
299+ return self.getSubscriptionsPageHeading()
300+
301+
302 class PersonVouchersView(LaunchpadFormView):
303 """Form for displaying and redeeming commercial subscription vouchers."""
304
305
306=== modified file 'lib/lp/registry/browser/tests/test_person_view.py'
307--- lib/lp/registry/browser/tests/test_person_view.py 2010-09-16 19:31:07 +0000
308+++ lib/lp/registry/browser/tests/test_person_view.py 2010-09-30 22:08:08 +0000
309@@ -16,6 +16,7 @@
310 from canonical.launchpad.interfaces.account import AccountStatus
311 from canonical.launchpad.interfaces.logintoken import ILoginTokenSet
312 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
313+from canonical.launchpad.webapp.interfaces import ILaunchBag
314 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
315 from canonical.testing import (
316 DatabaseFunctionalLayer,
317@@ -28,8 +29,8 @@
318 from lp.registry.browser.person import (
319 PersonEditView,
320 PersonView,
321- TeamInvitationView,
322- )
323+ TeamInvitationView)
324+
325
326 from lp.registry.interfaces.karma import IKarmaCacheManager
327 from lp.registry.interfaces.person import (
328@@ -779,3 +780,32 @@
329 self.assertEqual(
330 expected,
331 notifications[0].message)
332+
333+
334+class TestSubscriptionsView(TestCaseWithFactory):
335+
336+ layer = LaunchpadFunctionalLayer
337+
338+ def setUp(self):
339+ super(TestSubscriptionsView, self).setUp(
340+ user='test@canonical.com')
341+ self.user = getUtility(ILaunchBag).user
342+ self.person = self.factory.makePerson()
343+ self.other_person = self.factory.makePerson()
344+ self.team = self.factory.makeTeam(owner=self.user)
345+ self.team.addMember(self.person, self.user)
346+
347+ def test_unsubscribe_link_appears_for_user(self):
348+ login_person(self.person)
349+ view = create_view(self.person, '+subscriptions')
350+ self.assertTrue(view.canUnsubscribeFromBugTasks())
351+
352+ def test_unsubscribe_link_does_not_appear_for_not_user(self):
353+ login_person(self.other_person)
354+ view = create_view(self.person, '+subscriptions')
355+ self.assertFalse(view.canUnsubscribeFromBugTasks())
356+
357+ def test_unsubscribe_link_appears_for_team_member(self):
358+ login_person(self.person)
359+ view = create_initialized_view(self.team, '+subscriptions')
360+ self.assertTrue(view.canUnsubscribeFromBugTasks())
361
362=== added file 'lib/lp/registry/stories/person/xx-person-subscriptions.txt'
363--- lib/lp/registry/stories/person/xx-person-subscriptions.txt 1970-01-01 00:00:00 +0000
364+++ lib/lp/registry/stories/person/xx-person-subscriptions.txt 2010-09-30 22:08:08 +0000
365@@ -0,0 +1,131 @@
366+Subscriptions View
367+==================
368+
369+XXX: bdmurray 2010-09-24 bug=628411 This story is complete until we publish
370+the link that leads to the +subscriptions page.
371+
372+Any user can view the direct subscriptions that a person or team has to
373+blueprints, branches, bugs, merge proposals and questions.
374+
375+ >>> anon_browser.open('http://launchpad.dev/~ubuntu-team/+subscriptions')
376+ >>> print anon_browser.title
377+ Subscriptions : “Ubuntu Team” team
378+
379+The user can see that the Ubuntu Team does not have any direct bug
380+subscriptions.
381+
382+ >>> page_text = extract_text(find_main_content(anon_browser.contents))
383+ >>> "does not have any direct bug subscriptions" in page_text
384+ True
385+
386+Any user can see that Sample Person is subscribed to several bugs. The bug
387+subscriptions table includes the bug number, title and location.
388+
389+ >>> anon_browser.open('http://launchpad.dev/~name12/+subscriptions')
390+ >>> print extract_text(find_tag_by_id(
391+ ... anon_browser.contents, 'bug_subscriptions'))
392+ Summary
393+ In
394+ 13
395+ Launchpad CSS and JS is not testible
396+ Launchpad
397+ 4
398+ Reflow problems with complex page layouts
399+ Mozilla Firefox
400+ 9
401+ Thunderbird crashes
402+ thunderbird (Ubuntu)
403+ 1
404+ Firefox does not support SVG
405+ mozilla-firefox (Ubuntu)
406+
407+The bug subscriptions table also includes an unsubscribe link, if they have
408+permission to remove the subscription, for bugs to which the person or team is
409+subscribed.
410+
411+Sample Person can see and manage his direct bug subscriptions using the page
412+too. He chooses to view bug 13 - "Launchpad CSS and JS in not testible".
413+
414+ >>> sample_browser = setupBrowser(auth='Basic test@canonical.com:test')
415+ >>> sample_browser.open('http://bugs.launchpad.dev/~name12/+subscriptions')
416+ >>> unsub_link = sample_browser.getLink(
417+ ... id='unsubscribe-subscriber-12')
418+ >>> unsub_link.click()
419+ >>> print extract_text(find_tag_by_id(
420+ ... sample_browser.contents, 'nonportlets'))
421+ Unsubscribe from bug #13
422+ ...
423+
424+After viewing the +subscribe page Sample Person decides that they still want
425+to be subscribed to bug 13. They click cancel which takes them back to their
426++subscription page.
427+
428+ >>> cancel_link = sample_browser.getLink('Cancel')
429+ >>> cancel_link.click()
430+ >>> print sample_browser.title
431+ Subscriptions : Sample Person
432+
433+He chooses to unsubscribe from bug 9 - "Thunderbird crashes".
434+
435+ >>> sample_browser.getLink(url='/ubuntu/+source/thunderbird/+bug/9/+subscribe').click()
436+ >>> sample_browser.getControl("Unsubscribe me from this bug").selected = True
437+ >>> sample_browser.getControl("Continue").click()
438+ >>> print sample_browser.title
439+ Subscriptions : Sample Person
440+
441+Sample Person can see that bug 9 is no longer listed in his direct bug
442+subscriptions.
443+
444+ >>> sample_browser.open('http://bugs.launchpad.dev/~name12/+subscriptions')
445+ >>> print extract_text(find_tag_by_id(
446+ ... sample_browser.contents, 'bug_subscriptions'))
447+ Summary
448+ In
449+ 13
450+ Launchpad CSS and JS is not testible
451+ Launchpad
452+ 4
453+ Reflow problems with complex page layouts
454+ Mozilla Firefox
455+ 1
456+ Firefox does not support SVG
457+ mozilla-firefox (Ubuntu)
458+
459+Sample Person adds a bug subscription for a team, HWDB team, of which he is a
460+member.
461+
462+ >>> sample_browser.open('http://bugs.launchpad.dev/firefox/+bug/1/+addsubscriber')
463+ >>> sample_browser.getControl('Person').value = 'hwdb-team'
464+ >>> sample_browser.getControl('Subscribe user').click()
465+
466+Sample Person chooses to review the subscriptions for his HWDB team.
467+
468+ >>> sample_browser.open('https://bugs.launchpad.dev/~hwdb-team/+subscriptions')
469+ >>> print sample_browser.title
470+ Subscriptions : “HWDB Team” team
471+
472+ >>> print extract_text(find_tag_by_id(
473+ ... sample_browser.contents, 'bug_subscriptions'))
474+ Summary
475+ In
476+ 1
477+ Firefox does not support SVG
478+ mozilla-firefox (Ubuntu)
479+
480+Sample Person now chooses to unsubscribe HWDB team from bug 1.
481+
482+ >>> sample_browser.getLink(id='unsubscribe-subscriber-243630').click()
483+ >>> print extract_text(find_tag_by_id(
484+ ... sample_browser.contents, 'nonportlets'))
485+ Unsubscribe from bug #1
486+ ...
487+
488+ >>> sample_browser.getControl(
489+ ... 'Unsubscribe HWDB Team from this bug').selected = True
490+ >>> sample_browser.getControl('Continue').click()
491+
492+ >>> sample_browser.open('https://launchpad.dev/~hwdb-team/+subscriptions')
493+ >>> page_text = extract_text(
494+ ... find_main_content(sample_browser.contents))
495+ >>> "does not have any direct bug subscriptions" in page_text
496+ True
497
498=== modified file 'lib/lp/registry/stories/productseries/xx-productseries-delete.txt'
499--- lib/lp/registry/stories/productseries/xx-productseries-delete.txt 2010-03-03 00:40:03 +0000
500+++ lib/lp/registry/stories/productseries/xx-productseries-delete.txt 2010-09-30 22:08:08 +0000
501@@ -32,7 +32,7 @@
502 LookupError: ...
503
504 Sample person chooses to cancel the action and make the 1.0 series the focus
505-of development. He then removes the linked package which he beleives is
506+of development. He then removes the linked package which he believes is
507 bogus.
508
509 >>> owner_browser.getLink('Cancel').click()
510
511=== added file 'lib/lp/registry/templates/person-subscriptions.pt'
512--- lib/lp/registry/templates/person-subscriptions.pt 1970-01-01 00:00:00 +0000
513+++ lib/lp/registry/templates/person-subscriptions.pt 2010-09-30 22:08:08 +0000
514@@ -0,0 +1,73 @@
515+<html
516+ xmlns="http://www.w3.org/1999/xhtml"
517+ xmlns:tal="http://xml.zope.org/namespaces/tal"
518+ xmlns:metal="http://xml.zope.org/namespaces/metal"
519+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
520+ xml:lang="en"
521+ lang="en"
522+ dir="ltr"
523+ metal:use-macro="view/macro:page/main_side"
524+ i18n:domain="launchpad"
525+>
526+ <body>
527+
528+ <div metal:fill-slot="main"
529+ tal:define="sub_bugs view/subscribedBugTasks;
530+ sub_bug_batch sub_bugs/currentBatch">
531+ <tal:no_subscribed_bugs condition="not: sub_bug_batch|nothing">
532+ <p><tal:person content="context/fmt:displayname">Sample
533+ Person</tal:person> does not have any direct bug subscriptions.</p>
534+ </tal:no_subscribed_bugs>
535+
536+ <tal:subscribed_bugs condition="sub_bug_batch|nothing">
537+ <p>
538+ Direct bug subscriptions for
539+ <tal:person content="context/fmt:displayname">Sample Person</tal:person>
540+ </p>
541+ <div tal:replace="structure sub_bugs/@@+navigation-links-lower"/>
542+ <table id="bug_subscriptions" class="listing">
543+ <thead>
544+ <tr>
545+ <th colspan="3">Summary</th>
546+ <th>
547+ In
548+ </th>
549+ <th tal:condition="view/canUnsubscribeFromBugTasks">
550+ </th>
551+ </tr>
552+ </thead>
553+ <tr tal:repeat="sub_bug_task sub_bug_batch">
554+ <td class="icon left">
555+ <span tal:replace="structure sub_bug_task/image:icon" />
556+ </td>
557+ <td id="bug_number" style="text-align: right">
558+ <span tal:replace="sub_bug_task/bug/id" />
559+ </td>
560+ <td>
561+ <a tal:attributes="href sub_bug_task/fmt:url"
562+ tal:content="sub_bug_task/bug/title" />
563+ </td>
564+ <td tal:content="sub_bug_task/bugtargetdisplayname"
565+ tal:condition="sub_bug_task/bugtargetdisplayname|nothing"
566+ >mozilla-firefox (Ubuntu)</td>
567+ <td tal:condition="view/canUnsubscribeFromBugTasks"
568+ align="center">
569+ <a
570+ tal:attributes="
571+ title string:Unsubscribe ${context/fmt:displayname} from bug ${sub_bug_task/bug/id};
572+ id string:unsubscribe-subscriber-${context/id};
573+ href string:${sub_bug_task/fmt:url}/+subscribe;
574+ "
575+ >
576+ <img src="/@@/edit"
577+ tal:attributes="id string:unsubscribe-icon-${context/id};
578+ position string:relative"/>
579+ </a>
580+ </td>
581+ </tr>
582+ </table>
583+ </tal:subscribed_bugs>
584+
585+ </div>
586+</body>
587+</html>