Merge lp:~brian-murray/launchpad/limited-subscriptions-page into lp:launchpad
- limited-subscriptions-page
- Merge into devel
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 | ||||
Related bugs: |
|
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/
add in +subscriptions page
lib/lp/
add in PersonSubscript
subscribedB
fix a typo in getSimpleSearch
lib/lp/
created a new test for the +subscriptions page
lib/lp/
created the +subscriptions page
== Tests ==
bin/test -cvvt xx-person-
== Demo and QA ==
Login in as sample person and go to http://
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:/
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.
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.
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.
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:/
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?
Deryck Hodge (deryck) 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:/
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:/
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:/
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...
Guilherme Salgado (salgado) wrote : | # |
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:/
>
> 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:/
>
> 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:/
> 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 ...
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=
page here" />
Curtis Hovey (sinzui) wrote : | # |
I am review r11483,
I am seeing some problems as Sample Person when I try to manage my subscriptions
https:/
* 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:/
* 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:/
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.
Curtis Hovey (sinzui) wrote : | # |
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://
>>> 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_
>>> "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://
>>> bug_sub_table = find_tag_by_id(
... anon_browser.
>>> for tr in bug_sub_
... 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(
>>> sample_
>>> sample_
>>> print sample_
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_
>>> print sample_
Subscriptions: Sample Person
He chooses to unsubscribe from bug 2, "Blackhole Trash folder".
>>> sample_
>>> print sample_
Unsubscribe from bug #2
Sample Person unselects the checkbox and continues.
>>> sample_
>>> sample_
>>> print sample_
Subscriptions: Sample Person
Sample Person can see that bug #2 is not listed in his direct bug
subscriptions.
>>> print extract_
... sample_
Summary In
13 Launchp...
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.
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 ReturnToReferre
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.
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 ReturnToReferre
Guilherme Salgado (salgado) wrote : | # |
There's one thing which I overlooked in my analysis above: ReturnToReferre
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
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> |
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' registry/ browser/ person. py 2010-09-10 12:24:03 +0000 registry/ browser/ person. py 2010-09-10 22:30:12 +0000 ageHeading( ) ionsView( BugTaskSearchLi stingView) : sks(self) : searchTasks( None, user=self.user,
> --- lib/lp/
> +++ lib/lp/
> @@ -2396,6 +2397,42 @@
> return self.getSearchP
>
>
> +class PersonSubscript
> + """All the subscriptions for a person."""
> +
> + page_title = 'Subscriptions'
> +
> + def subscribedBugTa
> + """Return a BatchNavigator for distinct bug tasks to which the
> + person is subscribed."""
> + bugtasks = self.context.
bugtasks => bug_tasks.
> + order_by= '-date_ last_updated' , (BugTaskStatus. NEW, INCOMPLETE, CONFIRMED, TRIAGED, INPROGRESS, FIXCOMMITTED, FIXRELEASED) )
> + status=
> + BugTaskStatus.
> + BugTaskStatus.
> + BugTaskStatus.
> + BugTaskStatus.
> + BugTaskStatus.
> + BugTaskStatus.
You can outdent all of this by four spaces; we use four-space indents
per level in method calls.
> + append( task) append( task.bug)
> + sub_bugtasks = []
> + sub_bugs = []
> +
> + for task in bugtasks:
> + if task.bug not in sub_bugs:
> + sub_bugtasks.
> + sub_bugs.
Is this loop going to cause performance problems for people with a large
number of direct subscriptions?
> + return BatchNavigator( sub_bugtasks, self.request) sPageHeading( self): displayname ptionsPageHeadi ng() iew(LaunchpadFo rmView) : registry/ stories/ person/ xx-person- subscriptions. txt' registry/ stories/ person/ xx-person- subscriptions. txt 1970-01-01 00:00:00 +0000 registry/ stories/ person/ xx-person- subscriptions. txt 2010-09-10 22:30:12 +0000 launchpad. dev/~ubuntu- team/+subscript ions') text(find_ main_content( anon_browser. contents) )
> +
> + def getSubscription
> + """The header for the subscriptions page."""
> + return "Subscriptions for %s" % self.context.
> +
> + @property
> + def label(self):
> + return self.getSubscri
> +
> +
> class PersonVouchersV
> """Form for displaying and redeeming commercial subscription vouchers."""
>
> === added file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -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://
> + >>> 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_
> + >>> "does not have any direct bug subscriptions" in page_text
> + Tru...