Merge lp:~bac/launchpad/accordion-client-2 into lp:launchpad
- accordion-client-2
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Brad Crittenden |
Approved revision: | no longer in the source branch. |
Merged at revision: | 12700 |
Proposed branch: | lp:~bac/launchpad/accordion-client-2 |
Merge into: | lp:launchpad |
Diff against target: |
2175 lines (+1097/-197) 29 files modified
lib/lp/bugs/browser/bugsubscription.py (+7/-2) lib/lp/bugs/browser/bugtarget.py (+10/-3) lib/lp/bugs/browser/bugtask.py (+9/-9) lib/lp/bugs/browser/structuralsubscription.py (+36/-31) lib/lp/bugs/templates/bug-subscription-list.pt (+4/-2) lib/lp/bugs/templates/buglisting-default.pt (+13/-0) lib/lp/bugs/templates/bugtarget-bugs.pt (+17/-2) lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt (+39/-14) lib/lp/bugs/templates/bugtarget-subscription-list.pt (+4/-2) lib/lp/registry/browser/__init__.py (+12/-0) lib/lp/registry/browser/distribution.py (+34/-11) lib/lp/registry/browser/distributionsourcepackage.py (+28/-10) lib/lp/registry/browser/distroseries.py (+34/-10) lib/lp/registry/browser/milestone.py (+14/-2) lib/lp/registry/browser/product.py (+23/-32) lib/lp/registry/browser/productseries.py (+30/-18) lib/lp/registry/browser/project.py (+28/-16) lib/lp/registry/browser/tests/test_product.py (+6/-2) lib/lp/registry/browser/tests/test_subscription_links.py (+609/-0) lib/lp/registry/javascript/structural-subscription.js (+1/-1) lib/lp/registry/javascript/tests/test_structural_subscription.js (+17/-17) lib/lp/registry/templates/distribution-index.pt (+19/-0) lib/lp/registry/templates/distributionsourcepackage-index.pt (+14/-1) lib/lp/registry/templates/distroseries-index.pt (+20/-2) lib/lp/registry/templates/milestone-index.pt (+15/-2) lib/lp/registry/templates/product-index.pt (+2/-2) lib/lp/registry/templates/productseries-index.pt (+29/-6) lib/lp/registry/templates/project-index.pt (+19/-0) lib/lp/services/features/flags.py (+4/-0) |
To merge this branch: | bzr merge lp:~bac/launchpad/accordion-client-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benji York (community) | code | Approve | |
Review via email: mp+55361@code.launchpad.net |
Commit message
[r=benji][no-qa] Add new JS-driven structural subscription links, guarded by feature flag, to IStructuralSubs
Description of the change
= Summary =
The new links to subscribe to structural subscriptions are placed on the
overview and bugs facet pages for all IStructuralSubs
The old structural subscriptions had such a link only on the bugs facet
for those targets.
The new menu link called 'subscribe_
'subscribe' menu link. It is done conditionally on the feature flag.
I apologize for the overly large size of this branch.
== Proposed fix ==
The link is conditionally added to the various browser view code based
on the feature flag. The corresponding page templates are updated to
include the required JavaScript and the target for the overlay to hook
into the DOM.
For well-behaved pages, simply replacing the old link with the new on in
the navigation menu is enough to have the links rendered properly. (The
links are initially hidden and then activated by the JavaScript if it is
enabled on a supported browser.) The rendering is handled by the
'+global-actions' menu generation macro.
Some pages, however, don't use that mechanism and create the links
manually. The existing pattern was followed. For some of those, the
portlet's visibility is controlled by a condition based on the new link
being enabled, since it uses 'launchpad.
restrictive permission of any of the items in the portlet. That part is
pretty gross and suggestions for a cleaner approach would be welcome.
== Pre-implementation notes ==
Chats and contributions from almost everyone on the Yellow Squad.
== Implementation details ==
As above.
== Tests ==
bin/test -vvm lp.registry -t test_subscripti
== Demo and Q/A ==
Go to
https:/
https:/
https:/
https:/
Note that distributions have special rules as to whether a user can
subscribe. If a bug supervisor is set, then only members of the bug
supervisor team can subscribe themselves. An admin can also subscribe
himself. NOTE: we must ensure that a non-admin member of the bug
supervisor team is not allowed to subscribe others.
In order to turn the feature flag to to
https:/
To turn it on enter:
malone.
To turn the flag off enter:
malone.
Note you *must* include a trailing space at the end of the line.
= Launchpad lint =
All of the following lint items are false positives.
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
./lib/lp/
169: not well-formed (invalid token)
./lib/lp/
295: E301 expected 1 blank line, found 2
387: E301 expected 1 blank line, found 2
481: E301 expected 1 blank line, found 2
567: E301 expected 1 blank line, found 2
Brad Crittenden (bac) wrote : | # |
Thanks for the review and good suggestions Benji. I have incorporated all of them. Getting the anonymous tests to work was easy using the somewhat obscure 'no_login' parameter.
Preview Diff
1 | === modified file 'lib/lp/bugs/browser/bugsubscription.py' |
2 | --- lib/lp/bugs/browser/bugsubscription.py 2011-03-29 00:11:57 +0000 |
3 | +++ lib/lp/bugs/browser/bugsubscription.py 2011-03-29 22:36:23 +0000 |
4 | @@ -40,7 +40,7 @@ |
5 | ) |
6 | from lp.bugs.browser.bug import BugViewMixin |
7 | from lp.bugs.browser.structuralsubscription import ( |
8 | - StructuralSubscriptionJSMixin, |
9 | + expose_structural_subscription_data_to_js, |
10 | ) |
11 | from lp.bugs.enum import BugNotificationLevel, HIDDEN_BUG_NOTIFICATION_LEVELS |
12 | from lp.bugs.interfaces.bugsubscription import IBugSubscription |
13 | @@ -578,9 +578,14 @@ |
14 | return 'subscriber-%s' % self.subscription.person.id |
15 | |
16 | |
17 | -class BugSubscriptionListView(StructuralSubscriptionJSMixin, LaunchpadView): |
18 | +class BugSubscriptionListView(LaunchpadView): |
19 | """A view to show all a person's subscriptions to a bug.""" |
20 | |
21 | + def initialize(self): |
22 | + super(BugSubscriptionListView, self).initialize() |
23 | + expose_structural_subscription_data_to_js( |
24 | + self.context, self.request, self.user, self.subscriptions) |
25 | + |
26 | @property |
27 | def subscriptions(self): |
28 | return get_structural_subscriptions_for_bug( |
29 | |
30 | === modified file 'lib/lp/bugs/browser/bugtarget.py' |
31 | --- lib/lp/bugs/browser/bugtarget.py 2011-03-23 19:20:03 +0000 |
32 | +++ lib/lp/bugs/browser/bugtarget.py 2011-03-29 22:36:23 +0000 |
33 | @@ -99,7 +99,7 @@ |
34 | from lp.bugs.browser.bugrole import BugRoleMixin |
35 | from lp.bugs.browser.bugtask import BugTaskSearchListingView |
36 | from lp.bugs.browser.structuralsubscription import ( |
37 | - StructuralSubscriptionJSMixin, |
38 | + expose_structural_subscription_data_to_js, |
39 | ) |
40 | from lp.bugs.browser.widgets.bug import ( |
41 | BugTagsWidget, |
42 | @@ -1308,10 +1308,12 @@ |
43 | return 'Bugs in %s' % self.context.title |
44 | |
45 | def initialize(self): |
46 | - BugTaskSearchListingView.initialize(self) |
47 | + super(BugTargetBugsView, self).initialize() |
48 | bug_statuses_to_show = list(UNRESOLVED_BUGTASK_STATUSES) |
49 | if IDistroSeries.providedBy(self.context): |
50 | bug_statuses_to_show.append(BugTaskStatus.FIXRELEASED) |
51 | + expose_structural_subscription_data_to_js( |
52 | + self.context, self.request, self.user) |
53 | |
54 | @property |
55 | def can_have_external_bugtracker(self): |
56 | @@ -1565,9 +1567,14 @@ |
57 | return ProxiedLibraryFileAlias(patch.libraryfile, patch).http_url |
58 | |
59 | |
60 | -class TargetSubscriptionView(StructuralSubscriptionJSMixin, LaunchpadView): |
61 | +class TargetSubscriptionView(LaunchpadView): |
62 | """A view to show all a person's structural subscriptions to a target.""" |
63 | |
64 | + def initialize(self): |
65 | + super(TargetSubscriptionView, self).initialize() |
66 | + expose_structural_subscription_data_to_js( |
67 | + self.context, self.request, self.user, self.subscriptions) |
68 | + |
69 | @property |
70 | def subscriptions(self): |
71 | return get_structural_subscriptions_for_target( |
72 | |
73 | === modified file 'lib/lp/bugs/browser/bugtask.py' |
74 | --- lib/lp/bugs/browser/bugtask.py 2011-03-25 15:33:51 +0000 |
75 | +++ lib/lp/bugs/browser/bugtask.py 2011-03-29 22:36:23 +0000 |
76 | @@ -195,6 +195,9 @@ |
77 | BugTextView, |
78 | BugViewMixin, |
79 | ) |
80 | +from lp.bugs.browser.structuralsubscription import ( |
81 | + expose_structural_subscription_data_to_js, |
82 | + ) |
83 | from lp.bugs.browser.bugcomment import ( |
84 | build_comments_from_chunks, |
85 | group_comments_with_activity, |
86 | @@ -275,12 +278,11 @@ |
87 | from lp.services.fields import PersonChoice |
88 | from lp.services.propertycache import ( |
89 | cachedproperty, |
90 | - get_property_cache, |
91 | ) |
92 | |
93 | |
94 | DISPLAY_BUG_STATUS_FOR_PATCHES = { |
95 | - BugTaskStatus.NEW: True, |
96 | + BugTaskStatus.NEW: True, |
97 | BugTaskStatus.INCOMPLETE: True, |
98 | BugTaskStatus.INVALID: False, |
99 | BugTaskStatus.WONTFIX: False, |
100 | @@ -290,7 +292,7 @@ |
101 | BugTaskStatus.FIXCOMMITTED: True, |
102 | BugTaskStatus.FIXRELEASED: False, |
103 | BugTaskStatus.UNKNOWN: False, |
104 | - BugTaskStatus.EXPIRED: False |
105 | + BugTaskStatus.EXPIRED: False, |
106 | } |
107 | |
108 | |
109 | @@ -2205,11 +2207,6 @@ |
110 | return Link( |
111 | '+securitycontact', 'Change security contact', icon='edit') |
112 | |
113 | - def subscribe(self): |
114 | - user = getUtility(ILaunchBag).user |
115 | - if self.context.userCanAlterBugSubscription(user): |
116 | - return Link('+subscribe', 'Subscribe to bug mail', icon='edit') |
117 | - |
118 | def nominations(self): |
119 | return Link('+nominations', 'Review nominations', icon='bug') |
120 | |
121 | @@ -2346,6 +2343,9 @@ |
122 | # needing validation is already available internally to self. |
123 | self._validate(None, {}) |
124 | |
125 | + expose_structural_subscription_data_to_js( |
126 | + self.context, self.request, self.user) |
127 | + |
128 | @property |
129 | def columns_to_show(self): |
130 | """Returns a sequence of column names to be shown in the listing.""" |
131 | @@ -3196,7 +3196,7 @@ |
132 | # Hint to optimize when there are many bugtasks. |
133 | view.many_bugtasks = self.many_bugtasks |
134 | return view |
135 | - |
136 | + |
137 | def getBugTaskAndNominationViews(self): |
138 | """Return the IBugTasks and IBugNominations views for this bug. |
139 | |
140 | |
141 | === modified file 'lib/lp/bugs/browser/structuralsubscription.py' |
142 | --- lib/lp/bugs/browser/structuralsubscription.py 2011-03-24 15:31:53 +0000 |
143 | +++ lib/lp/bugs/browser/structuralsubscription.py 2011-03-29 22:36:23 +0000 |
144 | @@ -5,9 +5,9 @@ |
145 | |
146 | __all__ = [ |
147 | 'expose_enum_to_js', |
148 | + 'expose_structural_subscription_data_to_js', |
149 | 'expose_user_administered_teams_to_js', |
150 | 'expose_user_subscriptions_to_js', |
151 | - 'StructuralSubscriptionJSMixin', |
152 | 'StructuralSubscriptionMenuMixin', |
153 | 'StructuralSubscriptionTargetTraversalMixin', |
154 | 'StructuralSubscriptionView', |
155 | @@ -33,7 +33,10 @@ |
156 | from zope.traversing.browser import absoluteURL |
157 | |
158 | from canonical.launchpad.webapp.authorization import check_permission |
159 | -from canonical.launchpad.webapp.menu import Link |
160 | +from canonical.launchpad.webapp.menu import ( |
161 | + enabled_with_permission, |
162 | + Link, |
163 | + ) |
164 | from canonical.launchpad.webapp.publisher import ( |
165 | canonical_url, |
166 | LaunchpadView, |
167 | @@ -329,6 +332,14 @@ |
168 | class StructuralSubscriptionMenuMixin: |
169 | """Mix-in class providing the subscription add/edit menu link.""" |
170 | |
171 | + def _getSST(self): |
172 | + if IStructuralSubscriptionTarget.providedBy(self.context): |
173 | + sst = self.context |
174 | + else: |
175 | + # self.context is a view, and the target is its context |
176 | + sst = self.context.context |
177 | + return sst |
178 | + |
179 | def subscribe(self): |
180 | """The subscribe menu link. |
181 | |
182 | @@ -337,28 +348,41 @@ |
183 | and displays the edit icon. Otherwise, the link offers to subscribe |
184 | and displays the add icon. |
185 | """ |
186 | - if IStructuralSubscriptionTarget.providedBy(self.context): |
187 | - sst = self.context |
188 | - else: |
189 | - # self.context is a view, and the target is its context |
190 | - sst = self.context.context |
191 | + sst = self._getSST() |
192 | |
193 | # ProjectGroup milestones aren't really structural subscription |
194 | # targets as they're not real milestones, so you can't subscribe to |
195 | # them. |
196 | enabled = not IProjectGroupMilestone.providedBy(sst) |
197 | - |
198 | if sst.userHasBugSubscriptions(self.user): |
199 | text = 'Edit bug mail subscription' |
200 | icon = 'edit' |
201 | else: |
202 | text = 'Subscribe to bug mail' |
203 | icon = 'add' |
204 | - if enabled == False or ( |
205 | + if not enabled or ( |
206 | not sst.userCanAlterBugSubscription(self.user, self.user)): |
207 | - return Link('+subscribe', text, icon=icon, enabled=False) |
208 | - else: |
209 | - return Link('+subscribe', text, icon=icon, enabled=enabled) |
210 | + enabled = False |
211 | + return Link('+subscribe', text, icon=icon, enabled=enabled) |
212 | + |
213 | + @enabled_with_permission('launchpad.AnyPerson') |
214 | + def subscribe_to_bug_mail(self): |
215 | + sst = self._getSST() |
216 | + enabled = sst.userCanAlterBugSubscription(self.user, self.user) |
217 | + text = 'Subscribe to bug mail' |
218 | + return Link('#', text, icon='add', hidden=True, enabled=enabled) |
219 | + |
220 | + |
221 | +def expose_structural_subscription_data_to_js(context, request, |
222 | + user, subscriptions=None): |
223 | + """Expose all of the data for a structural subscription to JavaScript.""" |
224 | + expose_user_administered_teams_to_js(request, user) |
225 | + expose_enum_to_js(request, BugTaskImportance, 'importances') |
226 | + expose_enum_to_js(request, BugTaskStatus, 'statuses') |
227 | + if subscriptions is None: |
228 | + subscriptions = [] |
229 | + expose_user_subscriptions_to_js( |
230 | + user, subscriptions, request) |
231 | |
232 | |
233 | def expose_enum_to_js(request, enum, name): |
234 | @@ -424,25 +448,6 @@ |
235 | IJSONRequestCache(request).objects['subscription_info'] = info |
236 | |
237 | |
238 | -class StructuralSubscriptionJSMixin: |
239 | - """A mixin that exposes structural-subscription data in JS. |
240 | - |
241 | - Descendants of this mixin must define a `subscriptions` property |
242 | - that returns a list of the subscriptions to cache in the JS of the |
243 | - page. |
244 | - """ |
245 | - |
246 | - def initialize(self): |
247 | - super(StructuralSubscriptionJSMixin, self).initialize() |
248 | - expose_user_administered_teams_to_js(self.request, self.user) |
249 | - expose_user_subscriptions_to_js( |
250 | - self.user, self.subscriptions, self.request) |
251 | - expose_enum_to_js(self.request, BugTaskImportance, 'importances') |
252 | - expose_enum_to_js(self.request, BugTaskStatus, 'statuses') |
253 | - |
254 | - subscriptions = None # Override this. |
255 | - |
256 | - |
257 | class StructuralSubscribersPortletView(LaunchpadView): |
258 | """A simple view for displaying the subscribers portlet.""" |
259 | |
260 | |
261 | === modified file 'lib/lp/bugs/templates/bug-subscription-list.pt' |
262 | --- lib/lp/bugs/templates/bug-subscription-list.pt 2011-03-25 21:00:51 +0000 |
263 | +++ lib/lp/bugs/templates/bug-subscription-list.pt 2011-03-29 22:36:23 +0000 |
264 | @@ -12,9 +12,11 @@ |
265 | |
266 | <head> |
267 | <tal:head-epilogue metal:fill-slot="head_epilogue"> |
268 | - <script type="text/javascript"> |
269 | + <script type="text/javascript" |
270 | + tal:condition=" |
271 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
272 | LPS.use('lp.registry.structural_subscription', function(Y) { |
273 | - module = Y.lp.registry.structural_subscription; |
274 | + var module = Y.lp.registry.structural_subscription; |
275 | Y.on('domready', function() { |
276 | module.setup_bug_subscriptions( |
277 | {content_box: "#structural-subscription-content-box"}) |
278 | |
279 | === modified file 'lib/lp/bugs/templates/buglisting-default.pt' |
280 | --- lib/lp/bugs/templates/buglisting-default.pt 2011-02-24 14:53:05 +0000 |
281 | +++ lib/lp/bugs/templates/buglisting-default.pt 2011-03-29 22:36:23 +0000 |
282 | @@ -10,6 +10,16 @@ |
283 | <metal:block fill-slot="head_epilogue"> |
284 | <meta condition="not: view/should_show_bug_information" |
285 | name="robots" content="noindex,nofollow" /> |
286 | + <script type="text/javascript" |
287 | + tal:condition=" |
288 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
289 | + LPS.use('lp.registry.structural_subscription', function(Y) { |
290 | + var module = Y.lp.registry.structural_subscription; |
291 | + Y.on('domready', function() { |
292 | + module.setup({content_box: "#structural-subscription-content-box"}); |
293 | + }); |
294 | + }); |
295 | + </script> |
296 | </metal:block> |
297 | |
298 | <body> |
299 | @@ -61,6 +71,9 @@ |
300 | use-macro="context/@@+bugtask-macros-tableview/advanced_search_form" /> |
301 | </tal:show_advanced_form> |
302 | |
303 | + <div class="yui-u"> |
304 | + <div id="structural-subscription-content-box"></div> |
305 | + </div> |
306 | |
307 | </div> |
308 | <div tal:condition="view/bug_tracking_usage/enumvalue:UNKNOWN" |
309 | |
310 | === modified file 'lib/lp/bugs/templates/bugtarget-bugs.pt' |
311 | --- lib/lp/bugs/templates/bugtarget-bugs.pt 2010-12-20 16:01:50 +0000 |
312 | +++ lib/lp/bugs/templates/bugtarget-bugs.pt 2011-03-29 22:36:23 +0000 |
313 | @@ -12,11 +12,22 @@ |
314 | <metal:block fill-slot="head_epilogue"> |
315 | <meta tal:condition="not: view/bug_tracking_usage/enumvalue:LAUNCHPAD" |
316 | name="robots" content="noindex,nofollow" /> |
317 | + <script type="text/javascript" |
318 | + tal:condition=" |
319 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
320 | + LPS.use('lp.registry.structural_subscription', function(Y) { |
321 | + var module = Y.lp.registry.structural_subscription; |
322 | + Y.on('domready', function() { |
323 | + module.setup({content_box: "#structural-subscription-content-box"}); |
324 | + }); |
325 | + }); |
326 | + </script> |
327 | <style type="text/css"> |
328 | p#more-hot-bugs {float:right; margin-top:7px;} |
329 | </style> |
330 | - </metal:block> |
331 | +</metal:block> |
332 | <body> |
333 | + |
334 | <tal:side metal:fill-slot="side" |
335 | condition="view/bug_tracking_usage/enumvalue:LAUNCHPAD"> |
336 | <div id="involvement" class="portlet"> |
337 | @@ -155,7 +166,7 @@ |
338 | </table> |
339 | <p id="more-hot-bugs" |
340 | tal:condition="view/hot_bugs_info/has_more_bugs"> |
341 | - <a tal:attributes="href string:${context/fmt:url/+bugs}?orderby=-heat&field.status%3Alist=NEW&field.status%3Alist=INCOMPLETE_WITH_RESPONSE&field.status%3Alist=INCOMPLETE_WITHOUT_RESPONSE&field.status%3Alist=CONFIRMED&field.status%3Alist=TRIAGED&field.status%3Alist=INPROGRESS&field.status%3Alist=FIXCOMMITTED&field.omit_dupes=on">Show all bugs by heat</a> |
342 | + <a tal:attributes="href string:${context/fmt:url/+bugs}?orderby=-heat&field.status%3Alist=NEW&field.status%3Alist=INCOMPLETE_WITH_RESPONSE&field.status%3Alist=INCOMPLETE_WITHOUT_RESPONSE&field.status%3Alist=CONFIRMED&field.status%3Alist=TRIAGED&field.status%3Alist=INPROGRESS&field.status%3Alist=FIXCOMMITTED&field.omit_dupes=on">Show all bugs by heat</a> |
343 | </p> |
344 | </div> |
345 | |
346 | @@ -213,6 +224,10 @@ |
347 | </p> |
348 | </div> |
349 | |
350 | + <div class="yui-u"> |
351 | + <div id="structural-subscription-content-box"></div> |
352 | + </div> |
353 | + |
354 | </div><!-- main --> |
355 | </body> |
356 | </html> |
357 | |
358 | === modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt' |
359 | --- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2010-08-16 23:28:48 +0000 |
360 | +++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2011-03-29 22:36:23 +0000 |
361 | @@ -10,21 +10,46 @@ |
362 | <tbody id="bugfilters-portlet-content" |
363 | tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" /> |
364 | <tbody tal:define="menu context/menu:bugs"> |
365 | - <tr tal:define="subscribe_link menu/subscribe|nothing" |
366 | - tal:condition="python: subscribe_link and subscribe_link.enabled"> |
367 | - <td class="bugs-count" style="padding-top: 1em"> |
368 | - <a tal:attributes="href subscribe_link/url"> |
369 | - <img tal:attributes="src subscribe_link/icon_url" /> |
370 | - </a> |
371 | - </td> |
372 | - <td class="bugs-link"> |
373 | - <a tal:attributes="href subscribe_link/url" |
374 | - tal:content="subscribe_link/escapedtext" /> |
375 | - </td> |
376 | - </tr> |
377 | + |
378 | + <tal:advanced-structural-subscriptions |
379 | + condition="request/features/malone.advanced-structural-subscriptions.enabled"> |
380 | + <tr class="menu-link-subscribe_to_bug_mail invisible-link" |
381 | + tal:define="subscribe_link menu/subscribe_to_bug_mail|nothing" |
382 | + tal:condition="python: subscribe_link and subscribe_link.enabled"> |
383 | + <td class="bugs-count" style="padding-top: 3px"> |
384 | + <a tal:attributes="href subscribe_link/url"> |
385 | + <img tal:attributes="src subscribe_link/icon_url" /> |
386 | + </a> |
387 | + </td> |
388 | + <td class="bugs-link"> |
389 | + <a class="js-action" |
390 | + tal:attributes="href subscribe_link/url" |
391 | + tal:content="subscribe_link/escapedtext" /> |
392 | + </td> |
393 | + </tr> |
394 | + </tal:advanced-structural-subscriptions> |
395 | + |
396 | + <tal:not-advanced-structural-subscriptions |
397 | + condition="not: request/features/malone.advanced-structural-subscriptions.enabled"> |
398 | + <tr class="menu-link-subscribe" |
399 | + tal:define="subscribe_link menu/subscribe|nothing" |
400 | + tal:condition="python: subscribe_link and subscribe_link.enabled"> |
401 | + <td class="bugs-count" style="padding-top: 3px"> |
402 | + <a tal:attributes="href subscribe_link/url"> |
403 | + <img tal:attributes="src subscribe_link/icon_url" /> |
404 | + </a> |
405 | + </td> |
406 | + <td class="bugs-link"> |
407 | + <a tal:attributes="href subscribe_link/url" |
408 | + tal:content="subscribe_link/escapedtext" /> |
409 | + </td> |
410 | + </tr> |
411 | + </tal:not-advanced-structural-subscriptions> |
412 | + |
413 | <tr tal:define="review_nominations_link context/menu:bugs/nominations|nothing" |
414 | - tal:condition="review_nominations_link"> |
415 | - <td class="bugs-count" style="padding-top: 1em"> |
416 | + tal:condition="review_nominations_link" |
417 | + style="padding-top: 1em"> |
418 | + <td class="bugs-count"> |
419 | <a tal:attributes="href review_nominations_link/url"> |
420 | <img tal:attributes="src review_nominations_link/icon_url" /> |
421 | </a> |
422 | |
423 | === modified file 'lib/lp/bugs/templates/bugtarget-subscription-list.pt' |
424 | --- lib/lp/bugs/templates/bugtarget-subscription-list.pt 2011-03-25 21:00:51 +0000 |
425 | +++ lib/lp/bugs/templates/bugtarget-subscription-list.pt 2011-03-29 22:36:23 +0000 |
426 | @@ -12,9 +12,11 @@ |
427 | |
428 | <head> |
429 | <tal:head-epilogue metal:fill-slot="head_epilogue"> |
430 | - <script type="text/javascript"> |
431 | + <script type="text/javascript" |
432 | + tal:condition=" |
433 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
434 | LPS.use('lp.registry.structural_subscription', function(Y) { |
435 | - module = Y.lp.registry.structural_subscription; |
436 | + var module = Y.lp.registry.structural_subscription; |
437 | Y.on('domready', function() { |
438 | module.setup_bug_subscriptions( |
439 | {content_box: "#structural-subscription-content-box"}) |
440 | |
441 | === modified file 'lib/lp/registry/browser/__init__.py' |
442 | --- lib/lp/registry/browser/__init__.py 2011-02-21 15:22:33 +0000 |
443 | +++ lib/lp/registry/browser/__init__.py 2011-03-29 22:36:23 +0000 |
444 | @@ -6,6 +6,7 @@ |
445 | __metaclass__ = type |
446 | |
447 | __all__ = [ |
448 | + 'add_subscribe_link', |
449 | 'BaseRdfView', |
450 | 'get_status_counts', |
451 | 'MilestoneOverlayMixin', |
452 | @@ -38,6 +39,7 @@ |
453 | ) |
454 | from lp.registry.interfaces.productseries import IProductSeries |
455 | from lp.registry.interfaces.series import SeriesStatus |
456 | +from lp.services.features import getFeatureFlag |
457 | |
458 | |
459 | class StatusCount: |
460 | @@ -69,6 +71,16 @@ |
461 | for status in sorted(statuses, key=attrgetter(key))] |
462 | |
463 | |
464 | +def add_subscribe_link(links): |
465 | + """Based on a feature flag, add the correct link.""" |
466 | + use_advanced_features = getFeatureFlag( |
467 | + 'malone.advanced-structural-subscriptions.enabled') |
468 | + if use_advanced_features: |
469 | + links.append('subscribe_to_bug_mail') |
470 | + else: |
471 | + links.append('subscribe') |
472 | + |
473 | + |
474 | class MilestoneOverlayMixin: |
475 | """A mixin that provides the data for the milestoneoverlay script.""" |
476 | |
477 | |
478 | === modified file 'lib/lp/registry/browser/distribution.py' |
479 | --- lib/lp/registry/browser/distribution.py 2011-03-04 09:55:17 +0000 |
480 | +++ lib/lp/registry/browser/distribution.py 2011-03-29 22:36:23 +0000 |
481 | @@ -83,9 +83,14 @@ |
482 | ) |
483 | from lp.bugs.browser.bugtask import BugTargetTraversalMixin |
484 | from lp.bugs.browser.structuralsubscription import ( |
485 | + expose_structural_subscription_data_to_js, |
486 | + StructuralSubscriptionMenuMixin, |
487 | StructuralSubscriptionTargetTraversalMixin, |
488 | ) |
489 | -from lp.registry.browser import RegistryEditFormView |
490 | +from lp.registry.browser import ( |
491 | + add_subscribe_link, |
492 | + RegistryEditFormView, |
493 | + ) |
494 | from lp.registry.browser.announcement import HasAnnouncementsView |
495 | from lp.registry.browser.menu import ( |
496 | IRegistryCollectionNavigationMenu, |
497 | @@ -104,6 +109,7 @@ |
498 | MirrorSpeed, |
499 | ) |
500 | from lp.registry.interfaces.series import SeriesStatus |
501 | +from lp.services.features import getFeatureFlag |
502 | from lp.services.geoip.helpers import ( |
503 | ipaddress_from_request, |
504 | request_country, |
505 | @@ -265,8 +271,8 @@ |
506 | return Link('+unofficialmirrors', text, enabled=enabled, icon='info') |
507 | |
508 | |
509 | -class DistributionLinksMixin: |
510 | - """A mixing to provide common links to menus.""" |
511 | +class DistributionLinksMixin(StructuralSubscriptionMenuMixin): |
512 | + """A mixin to provide common links to menus.""" |
513 | |
514 | @enabled_with_permission('launchpad.Edit') |
515 | def edit(self): |
516 | @@ -278,7 +284,15 @@ |
517 | """A menu of context actions.""" |
518 | usedfor = IDistribution |
519 | facet = 'overview' |
520 | - links = ['edit'] |
521 | + |
522 | + @cachedproperty |
523 | + def links(self): |
524 | + links = ['edit'] |
525 | + use_advanced_features = getFeatureFlag( |
526 | + 'malone.advanced-structural-subscriptions.enabled') |
527 | + if use_advanced_features: |
528 | + links.append('subscribe_to_bug_mail') |
529 | + return links |
530 | |
531 | |
532 | class DistributionOverviewMenu(ApplicationMenu, DistributionLinksMixin): |
533 | @@ -448,13 +462,17 @@ |
534 | |
535 | usedfor = IDistribution |
536 | facet = 'bugs' |
537 | - links = ( |
538 | - 'bugsupervisor', |
539 | - 'securitycontact', |
540 | - 'cve', |
541 | - 'filebug', |
542 | - 'subscribe', |
543 | - ) |
544 | + |
545 | + @property |
546 | + def links(self): |
547 | + links = [ |
548 | + 'bugsupervisor', |
549 | + 'securitycontact', |
550 | + 'cve', |
551 | + 'filebug', |
552 | + ] |
553 | + add_subscribe_link(links) |
554 | + return links |
555 | |
556 | |
557 | class DistributionSpecificationsMenu(NavigationMenu, |
558 | @@ -594,6 +612,11 @@ |
559 | class DistributionView(HasAnnouncementsView, FeedsMixin): |
560 | """Default Distribution view class.""" |
561 | |
562 | + def initialize(self): |
563 | + super(DistributionView, self).initialize() |
564 | + expose_structural_subscription_data_to_js( |
565 | + self.context, self.request, self.user) |
566 | + |
567 | def linkedMilestonesForSeries(self, series): |
568 | """Return a string of linkified milestones in the series.""" |
569 | # Listify to remove repeated queries. |
570 | |
571 | === modified file 'lib/lp/registry/browser/distributionsourcepackage.py' |
572 | --- lib/lp/registry/browser/distributionsourcepackage.py 2011-03-23 16:28:51 +0000 |
573 | +++ lib/lp/registry/browser/distributionsourcepackage.py 2011-03-29 22:36:23 +0000 |
574 | @@ -61,9 +61,12 @@ |
575 | from lp.app.interfaces.launchpad import IServiceUsage |
576 | from lp.bugs.browser.bugtask import BugTargetTraversalMixin |
577 | from lp.bugs.browser.structuralsubscription import ( |
578 | + expose_structural_subscription_data_to_js, |
579 | + StructuralSubscriptionMenuMixin, |
580 | StructuralSubscriptionTargetTraversalMixin, |
581 | ) |
582 | from lp.bugs.interfaces.bug import IBugSet |
583 | +from lp.registry.browser import add_subscribe_link |
584 | from lp.registry.browser.pillar import PillarBugsMenu |
585 | from lp.registry.interfaces.distributionsourcepackage import ( |
586 | IDistributionSourcePackage, |
587 | @@ -123,9 +126,6 @@ |
588 | |
589 | class DistributionSourcePackageLinksMixin: |
590 | |
591 | - def subscribe(self): |
592 | - return Link('+subscribe', 'Subscribe to bug mail', icon='edit') |
593 | - |
594 | def publishinghistory(self): |
595 | return Link('+publishinghistory', 'Show publishing history') |
596 | |
597 | @@ -152,17 +152,22 @@ |
598 | |
599 | usedfor = IDistributionSourcePackage |
600 | facet = 'overview' |
601 | - links = [ |
602 | - 'subscribe', 'publishinghistory', 'edit', 'new_bugs', |
603 | - 'open_questions'] |
604 | + links = ['new_bugs', 'open_questions'] |
605 | |
606 | |
607 | class DistributionSourcePackageBugsMenu( |
608 | - PillarBugsMenu, DistributionSourcePackageLinksMixin): |
609 | + PillarBugsMenu, |
610 | + StructuralSubscriptionMenuMixin, |
611 | + DistributionSourcePackageLinksMixin): |
612 | |
613 | usedfor = IDistributionSourcePackage |
614 | facet = 'bugs' |
615 | - links = ['filebug', 'subscribe'] |
616 | + |
617 | + @cachedproperty |
618 | + def links(self): |
619 | + links = ['filebug'] |
620 | + add_subscribe_link(links) |
621 | + return links |
622 | |
623 | |
624 | class DistributionSourcePackageNavigation(Navigation, |
625 | @@ -217,12 +222,20 @@ |
626 | |
627 | |
628 | class DistributionSourcePackageActionMenu( |
629 | - NavigationMenu, DistributionSourcePackageLinksMixin): |
630 | + NavigationMenu, |
631 | + StructuralSubscriptionMenuMixin, |
632 | + DistributionSourcePackageLinksMixin): |
633 | """Action menu for distro source packages.""" |
634 | usedfor = IDistributionSourcePackageActionMenu |
635 | facet = 'overview' |
636 | title = 'Actions' |
637 | - links = ('publishing_history', 'change_log', 'subscribe', 'edit') |
638 | + |
639 | + @cachedproperty |
640 | + def links(self): |
641 | + links = ['publishing_history', 'change_log'] |
642 | + add_subscribe_link(links) |
643 | + links.append('edit') |
644 | + return links |
645 | |
646 | def publishing_history(self): |
647 | text = 'View full publishing history' |
648 | @@ -295,6 +308,11 @@ |
649 | """View class for DistributionSourcePackage.""" |
650 | implements(IDistributionSourcePackageActionMenu) |
651 | |
652 | + def initialize(self): |
653 | + super(DistributionSourcePackageView, self).initialize() |
654 | + expose_structural_subscription_data_to_js( |
655 | + self.context, self.request, self.user) |
656 | + |
657 | @property |
658 | def label(self): |
659 | return self.context.title |
660 | |
661 | === modified file 'lib/lp/registry/browser/distroseries.py' |
662 | --- lib/lp/registry/browser/distroseries.py 2011-03-29 18:09:43 +0000 |
663 | +++ lib/lp/registry/browser/distroseries.py 2011-03-29 22:36:23 +0000 |
664 | @@ -75,10 +75,14 @@ |
665 | ) |
666 | from lp.bugs.browser.bugtask import BugTargetTraversalMixin |
667 | from lp.bugs.browser.structuralsubscription import ( |
668 | + expose_structural_subscription_data_to_js, |
669 | StructuralSubscriptionMenuMixin, |
670 | StructuralSubscriptionTargetTraversalMixin, |
671 | ) |
672 | -from lp.registry.browser import MilestoneOverlayMixin |
673 | +from lp.registry.browser import ( |
674 | + add_subscribe_link, |
675 | + MilestoneOverlayMixin, |
676 | + ) |
677 | from lp.registry.enum import DistroSeriesDifferenceStatus |
678 | from lp.registry.interfaces.distroseries import IDistroSeries |
679 | from lp.registry.interfaces.distroseriesdifference import ( |
680 | @@ -186,9 +190,23 @@ |
681 | |
682 | usedfor = IDistroSeries |
683 | facet = 'overview' |
684 | - links = ['edit', 'reassign', 'driver', 'answers', |
685 | - 'packaging', 'needs_packaging', 'builds', 'queue', |
686 | - 'add_port', 'create_milestone', 'subscribe', 'admin'] |
687 | + |
688 | + @property |
689 | + def links(self): |
690 | + links = ['edit', |
691 | + 'reassign', |
692 | + 'driver', |
693 | + 'answers', |
694 | + 'packaging', |
695 | + 'needs_packaging', |
696 | + 'builds', |
697 | + 'queue', |
698 | + 'add_port', |
699 | + 'create_milestone', |
700 | + ] |
701 | + add_subscribe_link(links) |
702 | + links.append('admin') |
703 | + return links |
704 | |
705 | @enabled_with_permission('launchpad.Admin') |
706 | def edit(self): |
707 | @@ -252,11 +270,14 @@ |
708 | |
709 | usedfor = IDistroSeries |
710 | facet = 'bugs' |
711 | - links = ( |
712 | - 'cve', |
713 | - 'nominations', |
714 | - 'subscribe', |
715 | - ) |
716 | + |
717 | + @property |
718 | + def links(self): |
719 | + links = ['cve', |
720 | + 'nominations', |
721 | + ] |
722 | + add_subscribe_link(links) |
723 | + return links |
724 | |
725 | def cve(self): |
726 | return Link('+cve', 'CVE reports', icon='cve') |
727 | @@ -327,12 +348,15 @@ |
728 | self.context.datereleased = UTC_NOW |
729 | |
730 | |
731 | -class DistroSeriesView(MilestoneOverlayMixin): |
732 | +class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin): |
733 | |
734 | def initialize(self): |
735 | + super(DistroSeriesView, self).initialize() |
736 | self.displayname = '%s %s' % ( |
737 | self.context.distribution.displayname, |
738 | self.context.version) |
739 | + expose_structural_subscription_data_to_js( |
740 | + self.context, self.request, self.user) |
741 | |
742 | @property |
743 | def page_title(self): |
744 | |
745 | === modified file 'lib/lp/registry/browser/milestone.py' |
746 | --- lib/lp/registry/browser/milestone.py 2011-03-04 00:55:49 +0000 |
747 | +++ lib/lp/registry/browser/milestone.py 2011-03-29 22:36:23 +0000 |
748 | @@ -64,6 +64,7 @@ |
749 | get_status_counts, |
750 | RegistryDeleteViewMixin, |
751 | ) |
752 | +from lp.registry.browser import add_subscribe_link |
753 | from lp.registry.browser.product import ProductDownloadFileMixin |
754 | from lp.registry.interfaces.distroseries import IDistroSeries |
755 | from lp.registry.interfaces.milestone import ( |
756 | @@ -140,14 +141,25 @@ |
757 | class MilestoneContextMenu(ContextMenu, MilestoneLinkMixin): |
758 | """The menu for this milestone.""" |
759 | usedfor = IMilestone |
760 | - links = ['edit', 'subscribe', 'create_release'] |
761 | + |
762 | + @cachedproperty |
763 | + def links(self): |
764 | + links = ['edit'] |
765 | + add_subscribe_link(links) |
766 | + links.append('create_release') |
767 | + return links |
768 | |
769 | |
770 | class MilestoneOverviewNavigationMenu(NavigationMenu, MilestoneLinkMixin): |
771 | """Overview navigation menu for `IMilestone` objects.""" |
772 | usedfor = IMilestone |
773 | facet = 'overview' |
774 | - links = ('edit', 'delete', 'subscribe') |
775 | + |
776 | + @cachedproperty |
777 | + def links(self): |
778 | + links = ['edit', 'delete'] |
779 | + add_subscribe_link(links) |
780 | + return links |
781 | |
782 | |
783 | class MilestoneOverviewMenu(ApplicationMenu, MilestoneLinkMixin): |
784 | |
785 | === modified file 'lib/lp/registry/browser/product.py' |
786 | --- lib/lp/registry/browser/product.py 2011-03-23 15:55:44 +0000 |
787 | +++ lib/lp/registry/browser/product.py 2011-03-29 22:36:23 +0000 |
788 | @@ -153,14 +153,13 @@ |
789 | BugTargetTraversalMixin, |
790 | get_buglisting_search_filter_url, |
791 | ) |
792 | -from lp.bugs.interfaces.bugtask import ( |
793 | - RESOLVED_BUGTASK_STATUSES, |
794 | - BugTaskImportance, |
795 | - BugTaskStatus, |
796 | - ) |
797 | +from lp.bugs.interfaces.bugtask import RESOLVED_BUGTASK_STATUSES |
798 | from lp.code.browser.branchref import BranchRef |
799 | from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin |
800 | -from lp.registry.browser import BaseRdfView |
801 | +from lp.registry.browser import ( |
802 | + add_subscribe_link, |
803 | + BaseRdfView, |
804 | + ) |
805 | from lp.registry.browser.announcement import HasAnnouncementsView |
806 | from lp.registry.browser.branding import BrandingChangeView |
807 | from lp.registry.browser.menu import ( |
808 | @@ -173,8 +172,7 @@ |
809 | ) |
810 | from lp.registry.browser.productseries import get_series_branch_error |
811 | from lp.bugs.browser.structuralsubscription import ( |
812 | - expose_enum_to_js, |
813 | - expose_user_administered_teams_to_js, |
814 | + expose_structural_subscription_data_to_js, |
815 | StructuralSubscriptionMenuMixin, |
816 | StructuralSubscriptionTargetTraversalMixin, |
817 | ) |
818 | @@ -193,7 +191,6 @@ |
819 | from lp.registry.interfaces.productseries import IProductSeries |
820 | from lp.registry.interfaces.series import SeriesStatus |
821 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
822 | -from lp.services import features |
823 | from lp.services.fields import ( |
824 | PillarAliases, |
825 | PublicPersonChoice, |
826 | @@ -585,22 +582,12 @@ |
827 | facet = 'overview' |
828 | title = 'Actions' |
829 | |
830 | - @property |
831 | + @cachedproperty |
832 | def links(self): |
833 | links = ['edit', 'review_license', 'administer'] |
834 | - use_advanced_features = features.getFeatureFlag( |
835 | - 'advanced-structural-subscriptions.enabled') |
836 | - if use_advanced_features: |
837 | - links.append('subscribe_to_bug_mail') |
838 | - else: |
839 | - links.append('subscribe') |
840 | + add_subscribe_link(links) |
841 | return links |
842 | |
843 | - @enabled_with_permission('launchpad.AnyPerson') |
844 | - def subscribe_to_bug_mail(self): |
845 | - text = 'Subscribe to bug mail' |
846 | - return Link('#', text, icon='add', hidden=True) |
847 | - |
848 | |
849 | class ProductOverviewMenu(ApplicationMenu, ProductEditLinksMixin, |
850 | HasRecipesMenuMixin): |
851 | @@ -694,16 +681,20 @@ |
852 | |
853 | usedfor = IProduct |
854 | facet = 'bugs' |
855 | - links = ( |
856 | - 'filebug', |
857 | - 'bugsupervisor', |
858 | - 'securitycontact', |
859 | - 'cve', |
860 | - 'subscribe', |
861 | - 'configure_bugtracker', |
862 | - ) |
863 | configurable_bugtracker = True |
864 | |
865 | + @cachedproperty |
866 | + def links(self): |
867 | + links = [ |
868 | + 'filebug', |
869 | + 'bugsupervisor', |
870 | + 'securitycontact', |
871 | + 'cve', |
872 | + ] |
873 | + add_subscribe_link(links) |
874 | + links.append('configure_bugtracker') |
875 | + return links |
876 | + |
877 | |
878 | class ProductSpecificationsMenu(NavigationMenu, ProductEditLinksMixin, |
879 | HasSpecificationsMenuMixin): |
880 | @@ -1009,6 +1000,7 @@ |
881 | self.form = request.form_ng |
882 | |
883 | def initialize(self): |
884 | + super(ProductView, self).initialize() |
885 | self.status_message = None |
886 | product = self.context |
887 | title_field = IProduct['title'] |
888 | @@ -1028,9 +1020,8 @@ |
889 | self.show_programming_languages = bool( |
890 | self.context.programminglang or |
891 | check_permission('launchpad.Edit', self.context)) |
892 | - expose_user_administered_teams_to_js(self.request, self.user) |
893 | - expose_enum_to_js(self.request, BugTaskImportance, 'importances') |
894 | - expose_enum_to_js(self.request, BugTaskStatus, 'statuses') |
895 | + expose_structural_subscription_data_to_js( |
896 | + self.context, self.request, self.user) |
897 | |
898 | @property |
899 | def show_license_status(self): |
900 | |
901 | === modified file 'lib/lp/registry/browser/productseries.py' |
902 | --- lib/lp/registry/browser/productseries.py 2011-02-03 10:35:36 +0000 |
903 | +++ lib/lp/registry/browser/productseries.py 2011-03-29 22:36:23 +0000 |
904 | @@ -118,6 +118,7 @@ |
905 | ICodeImportSet, |
906 | ) |
907 | from lp.registry.browser import ( |
908 | + add_subscribe_link, |
909 | BaseRdfView, |
910 | MilestoneOverlayMixin, |
911 | RegistryDeleteViewMixin, |
912 | @@ -128,6 +129,7 @@ |
913 | PillarView, |
914 | ) |
915 | from lp.bugs.browser.structuralsubscription import ( |
916 | + expose_structural_subscription_data_to_js, |
917 | StructuralSubscriptionMenuMixin, |
918 | StructuralSubscriptionTargetTraversalMixin, |
919 | ) |
920 | @@ -279,19 +281,23 @@ |
921 | """The overview menu.""" |
922 | usedfor = IProductSeries |
923 | facet = 'overview' |
924 | - links = [ |
925 | - 'configure_bugtracker', |
926 | - 'create_milestone', |
927 | - 'create_release', |
928 | - 'delete', |
929 | - 'driver', |
930 | - 'edit', |
931 | - 'link_branch', |
932 | - 'rdf', |
933 | - 'set_branch', |
934 | - 'subscribe', |
935 | - 'ubuntupkg', |
936 | - ] |
937 | + |
938 | + @cachedproperty |
939 | + def links(self): |
940 | + links = [ |
941 | + 'configure_bugtracker', |
942 | + 'create_milestone', |
943 | + 'create_release', |
944 | + 'delete', |
945 | + 'driver', |
946 | + 'edit', |
947 | + 'link_branch', |
948 | + 'rdf', |
949 | + 'set_branch', |
950 | + ] |
951 | + add_subscribe_link(links) |
952 | + links.append('ubuntupkg') |
953 | + return links |
954 | |
955 | @enabled_with_permission('launchpad.Edit') |
956 | def configure_bugtracker(self): |
957 | @@ -380,11 +386,12 @@ |
958 | """The bugs menu.""" |
959 | usedfor = IProductSeries |
960 | facet = 'bugs' |
961 | - links = ( |
962 | - 'new', |
963 | - 'nominations', |
964 | - 'subscribe', |
965 | - ) |
966 | + |
967 | + @cachedproperty |
968 | + def links(self): |
969 | + links = ['new', 'nominations'] |
970 | + add_subscribe_link(links) |
971 | + return links |
972 | |
973 | def new(self): |
974 | """Return a link to report a bug in this series.""" |
975 | @@ -439,6 +446,11 @@ |
976 | class ProductSeriesView(LaunchpadView, MilestoneOverlayMixin): |
977 | """A view to show a series with translations.""" |
978 | |
979 | + def initialize(self): |
980 | + super(ProductSeriesView, self).initialize() |
981 | + expose_structural_subscription_data_to_js( |
982 | + self.context, self.request, self.user) |
983 | + |
984 | @property |
985 | def page_title(self): |
986 | """Return the HTML page title.""" |
987 | |
988 | === modified file 'lib/lp/registry/browser/project.py' |
989 | --- lib/lp/registry/browser/project.py 2011-01-21 08:30:55 +0000 |
990 | +++ lib/lp/registry/browser/project.py 2011-03-29 22:36:23 +0000 |
991 | @@ -74,7 +74,10 @@ |
992 | from lp.blueprints.browser.specificationtarget import ( |
993 | HasSpecificationsMenuMixin, |
994 | ) |
995 | -from lp.registry.browser import BaseRdfView |
996 | +from lp.registry.browser import ( |
997 | + add_subscribe_link, |
998 | + BaseRdfView, |
999 | + ) |
1000 | from lp.registry.browser.announcement import HasAnnouncementsView |
1001 | from lp.registry.browser.branding import BrandingChangeView |
1002 | from lp.registry.browser.menu import ( |
1003 | @@ -88,6 +91,8 @@ |
1004 | ProjectAddStepTwo, |
1005 | ) |
1006 | from lp.bugs.browser.structuralsubscription import ( |
1007 | + expose_structural_subscription_data_to_js, |
1008 | + StructuralSubscriptionMenuMixin, |
1009 | StructuralSubscriptionTargetTraversalMixin, |
1010 | ) |
1011 | from lp.registry.interfaces.product import IProductSet |
1012 | @@ -270,20 +275,20 @@ |
1013 | """Marker interface for views that use ProjectActionMenu.""" |
1014 | |
1015 | |
1016 | -class ProjectActionMenu(ProjectAdminMenuMixin, NavigationMenu): |
1017 | +class ProjectActionMenu(ProjectAdminMenuMixin, |
1018 | + StructuralSubscriptionMenuMixin, |
1019 | + NavigationMenu): |
1020 | |
1021 | usedfor = IProjectGroupActionMenu |
1022 | facet = 'overview' |
1023 | title = 'Action menu' |
1024 | - links = ('subscribe', 'edit', 'administer') |
1025 | |
1026 | - # XXX: salgado, bug=412178, 2009-08-10: This should be shown in the +index |
1027 | - # page of the project's bugs facet, but that would require too much work |
1028 | - # and I just want to convert this page to 3.0, so I'll leave it here for |
1029 | - # now. |
1030 | - def subscribe(self): |
1031 | - text = 'Subscribe to bug mail' |
1032 | - return Link('+subscribe', text, icon='edit') |
1033 | + @cachedproperty |
1034 | + def links(self): |
1035 | + links = [] |
1036 | + add_subscribe_link(links) |
1037 | + links.extend(['edit', 'administer']) |
1038 | + return links |
1039 | |
1040 | @enabled_with_permission('launchpad.Edit') |
1041 | def edit(self): |
1042 | @@ -323,24 +328,31 @@ |
1043 | return Link('+addquestion', text, icon='add') |
1044 | |
1045 | |
1046 | -class ProjectBugsMenu(ApplicationMenu): |
1047 | +class ProjectBugsMenu(StructuralSubscriptionMenuMixin, |
1048 | + ApplicationMenu): |
1049 | |
1050 | usedfor = IProjectGroup |
1051 | facet = 'bugs' |
1052 | - links = ['new', 'subscribe'] |
1053 | + |
1054 | + @cachedproperty |
1055 | + def links(self): |
1056 | + links = ['new'] |
1057 | + add_subscribe_link(links) |
1058 | + return links |
1059 | |
1060 | def new(self): |
1061 | text = 'Report a Bug' |
1062 | return Link('+filebug', text, icon='add') |
1063 | |
1064 | - def subscribe(self): |
1065 | - text = 'Subscribe to bug mail' |
1066 | - return Link('+subscribe', text, icon='edit') |
1067 | - |
1068 | |
1069 | class ProjectView(HasAnnouncementsView, FeedsMixin): |
1070 | implements(IProjectGroupActionMenu) |
1071 | |
1072 | + def initialize(self): |
1073 | + super(ProjectView, self).initialize() |
1074 | + expose_structural_subscription_data_to_js( |
1075 | + self.context, self.request, self.user) |
1076 | + |
1077 | @cachedproperty |
1078 | def has_many_projects(self): |
1079 | """Does the projectgroup have many sub projects. |
1080 | |
1081 | === modified file 'lib/lp/registry/browser/tests/test_product.py' |
1082 | --- lib/lp/registry/browser/tests/test_product.py 2011-03-07 19:53:40 +0000 |
1083 | +++ lib/lp/registry/browser/tests/test_product.py 2011-03-29 22:36:23 +0000 |
1084 | @@ -13,10 +13,14 @@ |
1085 | from zope.security.proxy import removeSecurityProxy |
1086 | |
1087 | from canonical.config import config |
1088 | -from canonical.launchpad.testing.pages import find_tag_by_id |
1089 | +from canonical.launchpad.testing.pages import ( |
1090 | + find_tag_by_id, |
1091 | + ) |
1092 | from canonical.testing.layers import DatabaseFunctionalLayer |
1093 | from lp.app.enums import ServiceUsage |
1094 | -from lp.registry.browser.product import ProductLicenseMixin |
1095 | +from lp.registry.browser.product import ( |
1096 | + ProductLicenseMixin, |
1097 | + ) |
1098 | from lp.registry.interfaces.product import ( |
1099 | License, |
1100 | IProductSet, |
1101 | |
1102 | === added file 'lib/lp/registry/browser/tests/test_subscription_links.py' |
1103 | --- lib/lp/registry/browser/tests/test_subscription_links.py 1970-01-01 00:00:00 +0000 |
1104 | +++ lib/lp/registry/browser/tests/test_subscription_links.py 2011-03-29 22:36:23 +0000 |
1105 | @@ -0,0 +1,609 @@ |
1106 | +# Copyright 2011 Canonical Ltd. This software is licensed under the |
1107 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1108 | + |
1109 | +"""Tests for subscription links.""" |
1110 | + |
1111 | +__metaclass__ = type |
1112 | + |
1113 | +import unittest |
1114 | +from zope.component import getUtility |
1115 | +from BeautifulSoup import BeautifulSoup |
1116 | + |
1117 | +from canonical.launchpad.webapp.interaction import ANONYMOUS |
1118 | +from canonical.launchpad.webapp.interfaces import ILaunchBag |
1119 | +from canonical.launchpad.webapp.publisher import canonical_url |
1120 | +from canonical.launchpad.webapp.servers import LaunchpadTestRequest |
1121 | +from canonical.launchpad.testing.pages import ( |
1122 | + first_tag_by_class, |
1123 | + ) |
1124 | +from canonical.testing.layers import DatabaseFunctionalLayer |
1125 | + |
1126 | +from lp.registry.interfaces.person import IPersonSet |
1127 | +from lp.services.features import ( |
1128 | + get_relevant_feature_controller, |
1129 | + ) |
1130 | +from lp.services.features.testing import FeatureFixture |
1131 | +from lp.testing import ( |
1132 | + celebrity_logged_in, |
1133 | + person_logged_in, |
1134 | + BrowserTestCase, |
1135 | + TestCaseWithFactory, |
1136 | + ) |
1137 | +from lp.testing.sampledata import ADMIN_EMAIL |
1138 | +from lp.testing.views import ( |
1139 | + create_initialized_view, |
1140 | + ) |
1141 | + |
1142 | + |
1143 | +class _TestStructSubs(TestCaseWithFactory): |
1144 | + """Test structural subscriptions base class. |
1145 | + |
1146 | + The link to structural subscriptions is controlled by the feature flag |
1147 | + 'malone.advanced-structural-subscriptions.enabled'. If it is false, the |
1148 | + old link leading to +subscribe is shown. If it is true then the new |
1149 | + JavaScript control is used. |
1150 | + """ |
1151 | + |
1152 | + layer = DatabaseFunctionalLayer |
1153 | + feature_flag = 'malone.advanced-structural-subscriptions.enabled' |
1154 | + |
1155 | + def setUp(self): |
1156 | + super(_TestStructSubs, self).setUp() |
1157 | + self.regular_user = self.factory.makePerson() |
1158 | + |
1159 | + def _create_scenario(self, user, flag): |
1160 | + with person_logged_in(user): |
1161 | + with FeatureFixture({self.feature_flag: flag}): |
1162 | + view = self.create_view(user) |
1163 | + self.contents = view.render() |
1164 | + old_link = first_tag_by_class( |
1165 | + self.contents, 'menu-link-subscribe') |
1166 | + new_link = first_tag_by_class( |
1167 | + self.contents, 'menu-link-subscribe_to_bug_mail') |
1168 | + return old_link, new_link |
1169 | + |
1170 | + def create_view(self, user): |
1171 | + request = LaunchpadTestRequest( |
1172 | + PATH_INFO='/', HTTP_COOKIE='', QUERY_STRING='') |
1173 | + request.features = get_relevant_feature_controller() |
1174 | + return create_initialized_view( |
1175 | + self.target, self.view, principal=user, |
1176 | + rootsite=self.rootsite, |
1177 | + request=request, current_request=False) |
1178 | + |
1179 | + def test_subscribe_link_feature_flag_off_owner(self): |
1180 | + old_link, new_link = self._create_scenario( |
1181 | + self.target.owner, None) |
1182 | + self.assertNotEqual(None, old_link, self.contents) |
1183 | + self.assertEqual(None, new_link, self.contents) |
1184 | + |
1185 | + def test_subscribe_link_feature_flag_on_owner(self): |
1186 | + # Test the new subscription link. |
1187 | + old_link, new_link = self._create_scenario( |
1188 | + self.target.owner, 'on') |
1189 | + self.assertEqual(None, old_link, self.contents) |
1190 | + self.assertNotEqual(None, new_link, self.contents) |
1191 | + |
1192 | + def test_subscribe_link_feature_flag_off_user(self): |
1193 | + old_link, new_link = self._create_scenario( |
1194 | + self.regular_user, None) |
1195 | + self.assertNotEqual(None, old_link, self.contents) |
1196 | + self.assertEqual(None, new_link, self.contents) |
1197 | + |
1198 | + def test_subscribe_link_feature_flag_on_user(self): |
1199 | + old_link, new_link = self._create_scenario( |
1200 | + self.regular_user, 'on') |
1201 | + self.assertEqual(None, old_link, self.contents) |
1202 | + self.assertNotEqual(None, new_link, self.contents) |
1203 | + |
1204 | + def test_subscribe_link_feature_flag_off_anonymous(self): |
1205 | + old_link, new_link = self._create_scenario( |
1206 | + ANONYMOUS, None) |
1207 | + # The old subscribe link is actually shown to anonymous users but the |
1208 | + # behavior has changed with the new link. |
1209 | + self.assertNotEqual(None, old_link, self.contents) |
1210 | + self.assertEqual(None, new_link, self.contents) |
1211 | + |
1212 | + def test_subscribe_link_feature_flag_on_anonymous(self): |
1213 | + old_link, new_link = self._create_scenario( |
1214 | + ANONYMOUS, 'on') |
1215 | + # The subscribe link is not shown to anonymous. |
1216 | + self.assertEqual(None, old_link, self.contents) |
1217 | + self.assertEqual(None, new_link, self.contents) |
1218 | + |
1219 | + |
1220 | +class TestProductViewStructSubs(_TestStructSubs): |
1221 | + """Test structural subscriptions on the product view.""" |
1222 | + |
1223 | + rootsite = None |
1224 | + view = '+index' |
1225 | + |
1226 | + def setUp(self): |
1227 | + super(TestProductViewStructSubs, self).setUp() |
1228 | + self.target = self.factory.makeProduct(official_malone=True) |
1229 | + |
1230 | + |
1231 | +class TestProductBugsStructSubs(TestProductViewStructSubs): |
1232 | + """Test structural subscriptions on the product bugs view.""" |
1233 | + |
1234 | + rootsite = 'bugs' |
1235 | + view = '+bugs-index' |
1236 | + |
1237 | + |
1238 | +class TestProjectGroupViewStructSubs(_TestStructSubs): |
1239 | + """Test structural subscriptions on the project group view.""" |
1240 | + |
1241 | + rootsite = None |
1242 | + view = '+index' |
1243 | + |
1244 | + def setUp(self): |
1245 | + super(TestProjectGroupViewStructSubs, self).setUp() |
1246 | + self.target = self.factory.makeProject() |
1247 | + self.factory.makeProduct( |
1248 | + project=self.target, official_malone=True) |
1249 | + |
1250 | + |
1251 | +class TestProjectGroupBugsStructSubs(TestProjectGroupViewStructSubs): |
1252 | + """Test structural subscriptions on the project group bugs view.""" |
1253 | + |
1254 | + rootsite = 'bugs' |
1255 | + view = '+bugs' |
1256 | + |
1257 | + |
1258 | +class TestProductSeriesViewStructSubs(_TestStructSubs): |
1259 | + """Test structural subscriptions on the product series view.""" |
1260 | + |
1261 | + rootsite = None |
1262 | + view = '+index' |
1263 | + |
1264 | + def setUp(self): |
1265 | + super(TestProductSeriesViewStructSubs, self).setUp() |
1266 | + self.target = self.factory.makeProductSeries() |
1267 | + |
1268 | + |
1269 | +class TestProductSeriesBugsStructSubs(TestProductSeriesViewStructSubs): |
1270 | + """Test structural subscriptions on the product series bugs view.""" |
1271 | + |
1272 | + rootsite = 'bugs' |
1273 | + view = '+bugs-index' |
1274 | + |
1275 | + def setUp(self): |
1276 | + super(TestProductSeriesBugsStructSubs, self).setUp() |
1277 | + with person_logged_in(self.target.product.owner): |
1278 | + self.target.product.official_malone = True |
1279 | + |
1280 | + |
1281 | +class TestDistributionSourcePackageViewStructSubs(_TestStructSubs): |
1282 | + """Test structural subscriptions on the distro src pkg view.""" |
1283 | + |
1284 | + rootsite = None |
1285 | + view = '+index' |
1286 | + |
1287 | + def setUp(self): |
1288 | + super(TestDistributionSourcePackageViewStructSubs, self).setUp() |
1289 | + distro = self.factory.makeDistribution() |
1290 | + with person_logged_in(distro.owner): |
1291 | + distro.official_malone = True |
1292 | + self.target = self.factory.makeDistributionSourcePackage( |
1293 | + distribution=distro) |
1294 | + self.regular_user = self.factory.makePerson() |
1295 | + |
1296 | + # DistributionSourcePackages do not have owners. |
1297 | + test_subscribe_link_feature_flag_off_owner = None |
1298 | + test_subscribe_link_feature_flag_on_owner = None |
1299 | + |
1300 | + |
1301 | +class TestDistributionSourcePackageBugsStructSubs( |
1302 | + TestDistributionSourcePackageViewStructSubs): |
1303 | + """Test structural subscriptions on the distro src pkg bugs view.""" |
1304 | + |
1305 | + rootsite = 'bugs' |
1306 | + view = '+bugs' |
1307 | + |
1308 | + |
1309 | +class TestDistroViewStructSubs(BrowserTestCase): |
1310 | + """Test structural subscriptions on the distribution view. |
1311 | + |
1312 | + Distributions are special. They are IStructuralSubscriptionTargets but |
1313 | + have complicated rules to ensure Ubuntu users don't subscribe and become |
1314 | + overwhelmed with email. If a distro does not have a bug supervisor set, |
1315 | + then anyone can create a structural subscription for themselves. If the |
1316 | + bug supervisor is set, then only people in the bug supervisor team can |
1317 | + subscribe themselves. Admins can subscribe anyone. |
1318 | + """ |
1319 | + |
1320 | + layer = DatabaseFunctionalLayer |
1321 | + feature_flag = 'malone.advanced-structural-subscriptions.enabled' |
1322 | + rootsite = None |
1323 | + view = '+index' |
1324 | + |
1325 | + def setUp(self): |
1326 | + super(TestDistroViewStructSubs, self).setUp() |
1327 | + self.target = self.factory.makeDistribution() |
1328 | + with person_logged_in(self.target.owner): |
1329 | + self.target.official_malone = True |
1330 | + self.regular_user = self.factory.makePerson() |
1331 | + |
1332 | + def _create_scenario(self, user, flag): |
1333 | + with person_logged_in(user): |
1334 | + with FeatureFixture({self.feature_flag: flag}): |
1335 | + logged_in_user = getUtility(ILaunchBag).user |
1336 | + no_login = logged_in_user is None |
1337 | + browser = self.getViewBrowser( |
1338 | + self.target, view_name=self.view, |
1339 | + rootsite=self.rootsite, |
1340 | + no_login=no_login, |
1341 | + user=logged_in_user) |
1342 | + self.contents = browser.contents |
1343 | + soup = BeautifulSoup(self.contents) |
1344 | + href = canonical_url( |
1345 | + self.target, rootsite=self.rootsite, |
1346 | + view_name='+subscribe') |
1347 | + old_link = soup.find('a', href=href) |
1348 | + new_link = first_tag_by_class( |
1349 | + self.contents, 'menu-link-subscribe_to_bug_mail') |
1350 | + return old_link, new_link |
1351 | + |
1352 | + def test_subscribe_link_feature_flag_off_owner(self): |
1353 | + old_link, new_link = self._create_scenario( |
1354 | + self.target.owner, None) |
1355 | + self.assertEqual(None, old_link, self.contents) |
1356 | + self.assertEqual(None, new_link, self.contents) |
1357 | + |
1358 | + def test_subscribe_link_feature_flag_on_owner(self): |
1359 | + old_link, new_link = self._create_scenario( |
1360 | + self.target.owner, 'on') |
1361 | + self.assertEqual(None, old_link, self.contents) |
1362 | + self.assertNotEqual(None, new_link, self.contents) |
1363 | + |
1364 | + def test_subscribe_link_feature_flag_off_user(self): |
1365 | + old_link, new_link = self._create_scenario( |
1366 | + self.regular_user, None) |
1367 | + self.assertEqual(None, old_link, self.contents) |
1368 | + self.assertEqual(None, new_link, self.contents) |
1369 | + |
1370 | + def test_subscribe_link_feature_flag_on_user_no_bug_super(self): |
1371 | + old_link, new_link = self._create_scenario( |
1372 | + self.regular_user, 'on') |
1373 | + self.assertEqual(None, old_link, self.contents) |
1374 | + self.assertNotEqual(None, new_link, self.contents) |
1375 | + |
1376 | + def test_subscribe_link_feature_flag_on_user_with_bug_super(self): |
1377 | + with celebrity_logged_in('admin'): |
1378 | + admin = getUtility(ILaunchBag).user |
1379 | + supervisor = self.factory.makePerson() |
1380 | + self.target.setBugSupervisor( |
1381 | + supervisor, admin) |
1382 | + old_link, new_link = self._create_scenario( |
1383 | + self.regular_user, 'on') |
1384 | + self.assertEqual(None, old_link, self.contents) |
1385 | + self.assertEqual(None, new_link, self.contents) |
1386 | + |
1387 | + def test_subscribe_link_feature_flag_off_anonymous(self): |
1388 | + old_link, new_link = self._create_scenario( |
1389 | + ANONYMOUS, None) |
1390 | + self.assertEqual(None, old_link, self.contents) |
1391 | + self.assertEqual(None, new_link, self.contents) |
1392 | + |
1393 | + def test_subscribe_link_feature_flag_on_anonymous(self): |
1394 | + old_link, new_link = self._create_scenario( |
1395 | + ANONYMOUS, 'on') |
1396 | + self.assertEqual(None, old_link, self.contents) |
1397 | + self.assertEqual(None, new_link, self.contents) |
1398 | + |
1399 | + def test_subscribe_link_feature_flag_off_bug_super(self): |
1400 | + with celebrity_logged_in('admin'): |
1401 | + admin = getUtility(ILaunchBag).user |
1402 | + self.target.setBugSupervisor( |
1403 | + self.regular_user, admin) |
1404 | + old_link, new_link = self._create_scenario( |
1405 | + self.regular_user, None) |
1406 | + self.assertEqual(None, old_link, self.contents) |
1407 | + self.assertEqual(None, new_link, self.contents) |
1408 | + |
1409 | + def test_subscribe_link_feature_flag_on_bug_super(self): |
1410 | + with celebrity_logged_in('admin'): |
1411 | + admin = getUtility(ILaunchBag).user |
1412 | + self.target.setBugSupervisor( |
1413 | + self.regular_user, admin) |
1414 | + old_link, new_link = self._create_scenario( |
1415 | + self.regular_user, 'on') |
1416 | + self.assertEqual(None, old_link, self.contents) |
1417 | + self.assertNotEqual(None, new_link, self.contents) |
1418 | + |
1419 | + def test_subscribe_link_feature_flag_off_admin(self): |
1420 | + admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL) |
1421 | + old_link, new_link = self._create_scenario( |
1422 | + admin, None) |
1423 | + self.assertEqual(None, old_link, self.contents) |
1424 | + self.assertEqual(None, new_link, self.contents) |
1425 | + |
1426 | + def test_subscribe_link_feature_flag_on_admin(self): |
1427 | + from lp.testing.sampledata import ADMIN_EMAIL |
1428 | + admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL) |
1429 | + old_link, new_link = self._create_scenario( |
1430 | + admin, 'on') |
1431 | + self.assertEqual(None, old_link, self.contents) |
1432 | + self.assertNotEqual(None, new_link, self.contents) |
1433 | + |
1434 | + |
1435 | +class TestDistroBugsStructSubs(TestDistroViewStructSubs): |
1436 | + """Test structural subscriptions on the distro bugs view.""" |
1437 | + |
1438 | + rootsite = 'bugs' |
1439 | + view = '+bugs-index' |
1440 | + |
1441 | + def test_subscribe_link_feature_flag_off_owner(self): |
1442 | + old_link, new_link = self._create_scenario( |
1443 | + self.target.owner, None) |
1444 | + self.assertNotEqual(None, old_link, self.contents) |
1445 | + self.assertEqual(None, new_link, self.contents) |
1446 | + |
1447 | + def test_subscribe_link_feature_flag_on_owner(self): |
1448 | + old_link, new_link = self._create_scenario( |
1449 | + self.target.owner, 'on') |
1450 | + self.assertEqual(None, old_link, self.contents) |
1451 | + self.assertNotEqual(None, new_link, self.contents) |
1452 | + |
1453 | + def test_subscribe_link_feature_flag_off_user(self): |
1454 | + old_link, new_link = self._create_scenario( |
1455 | + self.regular_user, None) |
1456 | + self.assertNotEqual(None, old_link, self.contents) |
1457 | + self.assertEqual(None, new_link, self.contents) |
1458 | + |
1459 | + def test_subscribe_link_feature_flag_on_user_no_bug_super(self): |
1460 | + old_link, new_link = self._create_scenario( |
1461 | + self.regular_user, 'on') |
1462 | + self.assertEqual(None, old_link, self.contents) |
1463 | + self.assertNotEqual(None, new_link, self.contents) |
1464 | + |
1465 | + def test_subscribe_link_feature_flag_on_user_with_bug_super(self): |
1466 | + with celebrity_logged_in('admin'): |
1467 | + admin = getUtility(ILaunchBag).user |
1468 | + supervisor = self.factory.makePerson() |
1469 | + self.target.setBugSupervisor( |
1470 | + supervisor, admin) |
1471 | + old_link, new_link = self._create_scenario( |
1472 | + self.regular_user, 'on') |
1473 | + self.assertEqual(None, old_link, self.contents) |
1474 | + self.assertEqual(None, new_link, self.contents) |
1475 | + |
1476 | + def test_subscribe_link_feature_flag_off_anonymous(self): |
1477 | + old_link, new_link = self._create_scenario( |
1478 | + ANONYMOUS, None) |
1479 | + self.assertNotEqual(None, old_link, self.contents) |
1480 | + self.assertEqual(None, new_link, self.contents) |
1481 | + |
1482 | + def test_subscribe_link_feature_flag_on_anonymous(self): |
1483 | + old_link, new_link = self._create_scenario( |
1484 | + ANONYMOUS, 'on') |
1485 | + self.assertEqual(None, old_link, self.contents) |
1486 | + self.assertEqual(None, new_link, self.contents) |
1487 | + |
1488 | + def test_subscribe_link_feature_flag_off_bug_super(self): |
1489 | + with celebrity_logged_in('admin'): |
1490 | + admin = getUtility(ILaunchBag).user |
1491 | + self.target.setBugSupervisor( |
1492 | + self.regular_user, admin) |
1493 | + old_link, new_link = self._create_scenario( |
1494 | + self.regular_user, None) |
1495 | + self.assertNotEqual(None, old_link, self.contents) |
1496 | + self.assertEqual(None, new_link, self.contents) |
1497 | + |
1498 | + def test_subscribe_link_feature_flag_on_bug_super(self): |
1499 | + with celebrity_logged_in('admin'): |
1500 | + admin = getUtility(ILaunchBag).user |
1501 | + self.target.setBugSupervisor( |
1502 | + self.regular_user, admin) |
1503 | + old_link, new_link = self._create_scenario( |
1504 | + self.regular_user, 'on') |
1505 | + self.assertEqual(None, old_link, self.contents) |
1506 | + self.assertNotEqual(None, new_link, self.contents) |
1507 | + |
1508 | + def test_subscribe_link_feature_flag_off_admin(self): |
1509 | + admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL) |
1510 | + old_link, new_link = self._create_scenario( |
1511 | + admin, None) |
1512 | + self.assertNotEqual(None, old_link, self.contents) |
1513 | + self.assertEqual(None, new_link, self.contents) |
1514 | + |
1515 | + def test_subscribe_link_feature_flag_on_admin(self): |
1516 | + from lp.testing.sampledata import ADMIN_EMAIL |
1517 | + admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL) |
1518 | + old_link, new_link = self._create_scenario( |
1519 | + admin, 'on') |
1520 | + self.assertEqual(None, old_link, self.contents) |
1521 | + self.assertNotEqual(None, new_link, self.contents) |
1522 | + |
1523 | + |
1524 | +class TestDistroMilestoneViewStructSubs(TestDistroViewStructSubs): |
1525 | + """Test structural subscriptions on the distro milestones.""" |
1526 | + |
1527 | + def setUp(self): |
1528 | + super(TestDistroMilestoneViewStructSubs, self).setUp() |
1529 | + self.distro = self.target |
1530 | + self.target = self.factory.makeMilestone(distribution=self.distro) |
1531 | + |
1532 | + def test_subscribe_link_feature_flag_off_owner(self): |
1533 | + old_link, new_link = self._create_scenario( |
1534 | + self.distro.owner, None) |
1535 | + self.assertNotEqual(None, old_link, self.contents) |
1536 | + self.assertEqual(None, new_link, self.contents) |
1537 | + |
1538 | + def test_subscribe_link_feature_flag_on_owner(self): |
1539 | + old_link, new_link = self._create_scenario( |
1540 | + self.distro.owner, 'on') |
1541 | + self.assertEqual(None, old_link, self.contents) |
1542 | + self.assertNotEqual(None, new_link, self.contents) |
1543 | + |
1544 | + def test_subscribe_link_feature_flag_off_user(self): |
1545 | + old_link, new_link = self._create_scenario( |
1546 | + self.regular_user, None) |
1547 | + self.assertNotEqual(None, old_link, self.contents) |
1548 | + self.assertEqual(None, new_link, self.contents) |
1549 | + |
1550 | + def test_subscribe_link_feature_flag_on_user_no_bug_super(self): |
1551 | + old_link, new_link = self._create_scenario( |
1552 | + self.regular_user, 'on') |
1553 | + self.assertEqual(None, old_link, self.contents) |
1554 | + self.assertNotEqual(None, new_link, self.contents) |
1555 | + |
1556 | + def test_subscribe_link_feature_flag_on_user_with_bug_super(self): |
1557 | + with celebrity_logged_in('admin'): |
1558 | + admin = getUtility(ILaunchBag).user |
1559 | + supervisor = self.factory.makePerson() |
1560 | + self.distro.setBugSupervisor( |
1561 | + supervisor, admin) |
1562 | + old_link, new_link = self._create_scenario( |
1563 | + self.regular_user, 'on') |
1564 | + self.assertEqual(None, old_link, self.contents) |
1565 | + self.assertNotEqual(None, new_link, self.contents) |
1566 | + |
1567 | + def test_subscribe_link_feature_flag_off_anonymous(self): |
1568 | + old_link, new_link = self._create_scenario( |
1569 | + ANONYMOUS, None) |
1570 | + self.assertNotEqual(None, old_link, self.contents) |
1571 | + self.assertEqual(None, new_link, self.contents) |
1572 | + |
1573 | + def test_subscribe_link_feature_flag_on_anonymous(self): |
1574 | + old_link, new_link = self._create_scenario( |
1575 | + ANONYMOUS, 'on') |
1576 | + self.assertEqual(None, old_link, self.contents) |
1577 | + self.assertEqual(None, new_link, self.contents) |
1578 | + |
1579 | + def test_subscribe_link_feature_flag_off_bug_super(self): |
1580 | + with celebrity_logged_in('admin'): |
1581 | + admin = getUtility(ILaunchBag).user |
1582 | + self.distro.setBugSupervisor( |
1583 | + self.regular_user, admin) |
1584 | + old_link, new_link = self._create_scenario( |
1585 | + self.regular_user, None) |
1586 | + self.assertNotEqual(None, old_link, self.contents) |
1587 | + self.assertEqual(None, new_link, self.contents) |
1588 | + |
1589 | + def test_subscribe_link_feature_flag_on_bug_super(self): |
1590 | + with celebrity_logged_in('admin'): |
1591 | + admin = getUtility(ILaunchBag).user |
1592 | + self.distro.setBugSupervisor( |
1593 | + self.regular_user, admin) |
1594 | + old_link, new_link = self._create_scenario( |
1595 | + self.regular_user, 'on') |
1596 | + self.assertEqual(None, old_link, self.contents) |
1597 | + self.assertNotEqual(None, new_link, self.contents) |
1598 | + |
1599 | + def test_subscribe_link_feature_flag_off_admin(self): |
1600 | + admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL) |
1601 | + old_link, new_link = self._create_scenario( |
1602 | + admin, None) |
1603 | + self.assertNotEqual(None, old_link, self.contents) |
1604 | + self.assertEqual(None, new_link, self.contents) |
1605 | + |
1606 | + def test_subscribe_link_feature_flag_on_admin(self): |
1607 | + from lp.testing.sampledata import ADMIN_EMAIL |
1608 | + admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL) |
1609 | + old_link, new_link = self._create_scenario( |
1610 | + admin, 'on') |
1611 | + self.assertEqual(None, old_link, self.contents) |
1612 | + self.assertNotEqual(None, new_link, self.contents) |
1613 | + |
1614 | + |
1615 | +class TestProductMilestoneViewStructSubs(TestDistroViewStructSubs): |
1616 | + """Test structural subscriptions on the product milestones.""" |
1617 | + |
1618 | + def setUp(self): |
1619 | + super(TestProductMilestoneViewStructSubs, self).setUp() |
1620 | + self.product = self.factory.makeProduct() |
1621 | + with person_logged_in(self.product.owner): |
1622 | + self.product.official_malone = True |
1623 | + self.regular_user = self.factory.makePerson() |
1624 | + self.target = self.factory.makeMilestone(product=self.product) |
1625 | + |
1626 | + def test_subscribe_link_feature_flag_off_owner(self): |
1627 | + old_link, new_link = self._create_scenario( |
1628 | + self.product.owner, None) |
1629 | + self.assertNotEqual(None, old_link, self.contents) |
1630 | + self.assertEqual(None, new_link, self.contents) |
1631 | + |
1632 | + def test_subscribe_link_feature_flag_on_owner(self): |
1633 | + old_link, new_link = self._create_scenario( |
1634 | + self.product.owner, 'on') |
1635 | + self.assertEqual(None, old_link, self.contents) |
1636 | + self.assertNotEqual(None, new_link, self.contents) |
1637 | + |
1638 | + def test_subscribe_link_feature_flag_off_user(self): |
1639 | + old_link, new_link = self._create_scenario( |
1640 | + self.regular_user, None) |
1641 | + self.assertNotEqual(None, old_link, self.contents) |
1642 | + self.assertEqual(None, new_link, self.contents) |
1643 | + |
1644 | + # There are no special bug supervisor rules for products. |
1645 | + test_subscribe_link_feature_flag_on_user_no_bug_super = None |
1646 | + test_subscribe_link_feature_flag_on_user_with_bug_super = None |
1647 | + test_subscribe_link_feature_flag_off_bug_super = None |
1648 | + test_subscribe_link_feature_flag_on_bug_super = None |
1649 | + |
1650 | + def test_subscribe_link_feature_flag_off_anonymous(self): |
1651 | + old_link, new_link = self._create_scenario( |
1652 | + ANONYMOUS, None) |
1653 | + self.assertNotEqual(None, old_link, self.contents) |
1654 | + self.assertEqual(None, new_link, self.contents) |
1655 | + |
1656 | + def test_subscribe_link_feature_flag_on_anonymous(self): |
1657 | + old_link, new_link = self._create_scenario( |
1658 | + ANONYMOUS, 'on') |
1659 | + self.assertEqual(None, old_link, self.contents) |
1660 | + self.assertEqual(None, new_link, self.contents) |
1661 | + |
1662 | + def test_subscribe_link_feature_flag_off_admin(self): |
1663 | + admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL) |
1664 | + old_link, new_link = self._create_scenario( |
1665 | + admin, None) |
1666 | + self.assertNotEqual(None, old_link, self.contents) |
1667 | + self.assertEqual(None, new_link, self.contents) |
1668 | + |
1669 | + def test_subscribe_link_feature_flag_on_admin(self): |
1670 | + from lp.testing.sampledata import ADMIN_EMAIL |
1671 | + admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL) |
1672 | + old_link, new_link = self._create_scenario( |
1673 | + admin, 'on') |
1674 | + self.assertEqual(None, old_link, self.contents) |
1675 | + self.assertNotEqual(None, new_link, self.contents) |
1676 | + |
1677 | + |
1678 | +class TestProductSeriesMilestoneViewStructSubs( |
1679 | + TestProductMilestoneViewStructSubs): |
1680 | + """Test structural subscriptions on the product series milestones.""" |
1681 | + |
1682 | + def setUp(self): |
1683 | + super(TestProductSeriesMilestoneViewStructSubs, self).setUp() |
1684 | + self.productseries = self.factory.makeProductSeries() |
1685 | + with person_logged_in(self.productseries.product.owner): |
1686 | + self.productseries.product.official_malone = True |
1687 | + self.regular_user = self.factory.makePerson() |
1688 | + self.target = self.factory.makeMilestone( |
1689 | + productseries=self.productseries) |
1690 | + |
1691 | + |
1692 | +def test_suite(): |
1693 | + """Return the `IStructuralSubscriptionTarget` TestSuite.""" |
1694 | + |
1695 | + # Manually construct the test suite to avoid having tests from the base |
1696 | + # class _TestStructSubs run. |
1697 | + suite = unittest.TestSuite() |
1698 | + suite.addTest(unittest.makeSuite(TestProductViewStructSubs)) |
1699 | + suite.addTest(unittest.makeSuite(TestProductBugsStructSubs)) |
1700 | + suite.addTest(unittest.makeSuite(TestProductSeriesViewStructSubs)) |
1701 | + suite.addTest(unittest.makeSuite(TestProductSeriesBugsStructSubs)) |
1702 | + suite.addTest(unittest.makeSuite(TestProjectGroupViewStructSubs)) |
1703 | + suite.addTest(unittest.makeSuite(TestProjectGroupBugsStructSubs)) |
1704 | + suite.addTest(unittest.makeSuite( |
1705 | + TestDistributionSourcePackageViewStructSubs)) |
1706 | + suite.addTest(unittest.makeSuite( |
1707 | + TestDistributionSourcePackageBugsStructSubs)) |
1708 | + suite.addTest(unittest.makeSuite(TestDistroViewStructSubs)) |
1709 | + suite.addTest(unittest.makeSuite(TestDistroBugsStructSubs)) |
1710 | + suite.addTest(unittest.makeSuite(TestDistroMilestoneViewStructSubs)) |
1711 | + suite.addTest(unittest.makeSuite(TestProductMilestoneViewStructSubs)) |
1712 | + suite.addTest(unittest.makeSuite( |
1713 | + TestProductSeriesMilestoneViewStructSubs)) |
1714 | + return suite |
1715 | |
1716 | === modified file 'lib/lp/registry/javascript/structural-subscription.js' |
1717 | --- lib/lp/registry/javascript/structural-subscription.js 2011-03-29 13:02:44 +0000 |
1718 | +++ lib/lp/registry/javascript/structural-subscription.js 2011-03-29 22:36:23 +0000 |
1719 | @@ -80,7 +80,7 @@ |
1720 | tags: [], |
1721 | find_all_tags: false, |
1722 | importances: [], |
1723 | - statuses: [], |
1724 | + statuses: [] |
1725 | }; |
1726 | |
1727 | // Set the notification level. |
1728 | |
1729 | === modified file 'lib/lp/registry/javascript/tests/test_structural_subscription.js' |
1730 | --- lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-03-29 13:02:44 +0000 |
1731 | +++ lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-03-29 22:36:23 +0000 |
1732 | @@ -87,7 +87,7 @@ |
1733 | LP.cache.statuses = []; |
1734 | |
1735 | this.configuration = { |
1736 | - content_box: content_box_id, |
1737 | + content_box: content_box_id |
1738 | }; |
1739 | this.content_node = create_test_node(); |
1740 | Y.one('body').appendChild(this.content_node); |
1741 | @@ -631,7 +631,7 @@ |
1742 | TestBugFilter.prototype = { |
1743 | 'getAttrs': function () { |
1744 | return {}; |
1745 | - }, |
1746 | + } |
1747 | }; |
1748 | |
1749 | // Now we need an lp_client that will appear to succesfully create |
1750 | @@ -648,7 +648,7 @@ |
1751 | 'patch': function(uri, representation, config, headers) { |
1752 | config.on.failure(true, {'status':400}); |
1753 | patch_failed = true; |
1754 | - }, |
1755 | + } |
1756 | }; |
1757 | module.lp_client = new TestClient(); |
1758 | |
1759 | @@ -661,7 +661,7 @@ |
1760 | // Delete should have been called and the patch has failed. |
1761 | Assert.isTrue(delete_called); |
1762 | Assert.isTrue(patch_failed); |
1763 | - }, |
1764 | + } |
1765 | |
1766 | })); |
1767 | |
1768 | @@ -707,7 +707,7 @@ |
1769 | var form_data = { |
1770 | name: ['filter description'], |
1771 | events: [], |
1772 | - filters: [], |
1773 | + filters: [] |
1774 | }; |
1775 | var patch_data = module._extract_form_data(form_data); |
1776 | Assert.areEqual(patch_data.description, form_data.name[0]); |
1777 | @@ -719,7 +719,7 @@ |
1778 | var form_data = { |
1779 | name: [' filter description '], |
1780 | events: [], |
1781 | - filters: [], |
1782 | + filters: [] |
1783 | }; |
1784 | var patch_data = module._extract_form_data(form_data); |
1785 | Assert.areEqual('filter description', patch_data.description); |
1786 | @@ -729,7 +729,7 @@ |
1787 | var form_data = { |
1788 | name: [], |
1789 | events: ['added-or-closed'], |
1790 | - filters: [], |
1791 | + filters: [] |
1792 | }; |
1793 | var patch_data = module._extract_form_data(form_data); |
1794 | Assert.areEqual( |
1795 | @@ -740,7 +740,7 @@ |
1796 | var form_data = { |
1797 | name: [], |
1798 | events: [], |
1799 | - filters: ['filter-comments'], |
1800 | + filters: ['filter-comments'] |
1801 | }; |
1802 | var patch_data = module._extract_form_data(form_data); |
1803 | Assert.areEqual( |
1804 | @@ -751,7 +751,7 @@ |
1805 | var form_data = { |
1806 | name: [], |
1807 | events: [], |
1808 | - filters: [], |
1809 | + filters: [] |
1810 | }; |
1811 | var patch_data = module._extract_form_data(form_data); |
1812 | Assert.areEqual( |
1813 | @@ -766,7 +766,7 @@ |
1814 | tags: ['one two THREE'], |
1815 | tag_match: [''], |
1816 | importances: [], |
1817 | - statuses: [], |
1818 | + statuses: [] |
1819 | }; |
1820 | var patch_data = module._extract_form_data(form_data); |
1821 | // Note that the tags are converted to lower case. |
1822 | @@ -782,7 +782,7 @@ |
1823 | tags: ['tag'], |
1824 | tag_match: ['match-all'], |
1825 | importances: [], |
1826 | - statuses: [], |
1827 | + statuses: [] |
1828 | }; |
1829 | var patch_data = module._extract_form_data(form_data); |
1830 | Assert.isTrue(patch_data.find_all_tags); |
1831 | @@ -796,7 +796,7 @@ |
1832 | tags: ['tag'], |
1833 | tag_match: [], |
1834 | importances: [], |
1835 | - statuses: [], |
1836 | + statuses: [] |
1837 | }; |
1838 | var patch_data = module._extract_form_data(form_data); |
1839 | Assert.isFalse(patch_data.find_all_tags); |
1840 | @@ -813,16 +813,16 @@ |
1841 | tags: ['tag'], |
1842 | tag_match: ['match-all'], |
1843 | importances: ['importance1'], |
1844 | - statuses: ['status1'], |
1845 | + statuses: ['status1'] |
1846 | }; |
1847 | var patch_data = module._extract_form_data(form_data); |
1848 | // Since advanced-filter isn't set, all the advanced values should |
1849 | // be empty/false despite the form values. |
1850 | Assert.isFalse(patch_data.find_all_tags); |
1851 | - ArrayAssert.isEmpty(patch_data.tags) |
1852 | - ArrayAssert.isEmpty(patch_data.importances) |
1853 | - ArrayAssert.isEmpty(patch_data.statuses) |
1854 | - }, |
1855 | + ArrayAssert.isEmpty(patch_data.tags); |
1856 | + ArrayAssert.isEmpty(patch_data.importances); |
1857 | + ArrayAssert.isEmpty(patch_data.statuses); |
1858 | + } |
1859 | |
1860 | })); |
1861 | |
1862 | |
1863 | === modified file 'lib/lp/registry/templates/distribution-index.pt' |
1864 | --- lib/lp/registry/templates/distribution-index.pt 2010-10-10 21:54:16 +0000 |
1865 | +++ lib/lp/registry/templates/distribution-index.pt 2011-03-29 22:36:23 +0000 |
1866 | @@ -5,6 +5,22 @@ |
1867 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1868 | metal:use-macro="view/macro:page/main_side" |
1869 | i18n:domain="launchpad"> |
1870 | + |
1871 | + <head> |
1872 | + <tal:head-epilogue metal:fill-slot="head_epilogue"> |
1873 | + <script type="text/javascript" |
1874 | + tal:condition=" |
1875 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
1876 | + LPS.use('lp.registry.structural_subscription', function(Y) { |
1877 | + var module = Y.lp.registry.structural_subscription; |
1878 | + Y.on('domready', function() { |
1879 | + module.setup({content_box: "#structural-subscription-content-box"}); |
1880 | + }); |
1881 | + }); |
1882 | + </script> |
1883 | + </tal:head-epilogue> |
1884 | + </head> |
1885 | + |
1886 | <body> |
1887 | <tal:heading metal:fill-slot="heading"> |
1888 | <h1 tal:content="context/title">project title</h1> |
1889 | @@ -69,6 +85,9 @@ |
1890 | |
1891 | <div tal:replace="structure context/@@+portlet-coming-sprints" /> |
1892 | </div> |
1893 | + <div class="yui-u"> |
1894 | + <div id="structural-subscription-content-box"></div> |
1895 | + </div> |
1896 | </div> |
1897 | </tal:main> |
1898 | |
1899 | |
1900 | === modified file 'lib/lp/registry/templates/distributionsourcepackage-index.pt' |
1901 | --- lib/lp/registry/templates/distributionsourcepackage-index.pt 2011-03-23 16:28:51 +0000 |
1902 | +++ lib/lp/registry/templates/distributionsourcepackage-index.pt 2011-03-29 22:36:23 +0000 |
1903 | @@ -16,6 +16,16 @@ |
1904 | tal:attributes="src string:${lp_js}/soyuz/base.js"></script> |
1905 | </tal:archive_js> |
1906 | </tal:devmode> |
1907 | + <script type="text/javascript" |
1908 | + tal:condition=" |
1909 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
1910 | + LPS.use('lp.registry.structural_subscription', function(Y) { |
1911 | + var module = Y.lp.registry.structural_subscription; |
1912 | + Y.on('domready', function() { |
1913 | + module.setup({content_box: "#structural-subscription-content-box"}); |
1914 | + }); |
1915 | + }); |
1916 | + </script> |
1917 | </metal:block> |
1918 | |
1919 | <tal:side metal:fill-slot="side"> |
1920 | @@ -28,6 +38,9 @@ |
1921 | </tal:side> |
1922 | |
1923 | <tal:main metal:fill-slot="main"> |
1924 | + <div class="yui-u"> |
1925 | + <div id="structural-subscription-content-box"></div> |
1926 | + </div> |
1927 | <div class="top-portlet" id="bugs-and-questions-summary" |
1928 | tal:define="newbugs context/new_bugtasks/count; |
1929 | open_questions view/open_questions/count"> |
1930 | @@ -169,7 +182,7 @@ |
1931 | <tr tal:attributes="id string:pub${pubid}" style="display: none"> |
1932 | <td colspan="3"> |
1933 | <div class="package-details" |
1934 | - tal:attributes="id string:pub${pubid}-container" /> |
1935 | + tal:attributes="id string:pub${pubid}-container"></div> |
1936 | </td> |
1937 | </tr> |
1938 | |
1939 | |
1940 | === modified file 'lib/lp/registry/templates/distroseries-index.pt' |
1941 | --- lib/lp/registry/templates/distroseries-index.pt 2010-10-10 21:54:16 +0000 |
1942 | +++ lib/lp/registry/templates/distroseries-index.pt 2011-03-29 22:36:23 +0000 |
1943 | @@ -5,6 +5,7 @@ |
1944 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1945 | metal:use-macro="view/macro:page/main_side" |
1946 | i18n:domain="launchpad"> |
1947 | + |
1948 | <body> |
1949 | <metal:block fill-slot="head_epilogue"> |
1950 | <metal:yui-dependencies |
1951 | @@ -12,6 +13,16 @@ |
1952 | <script id="milestone-script" type="text/javascript" |
1953 | tal:condition="context/menu:overview/create_milestone/enabled" |
1954 | tal:content="view/register_milestone_script"></script> |
1955 | + <script type="text/javascript" |
1956 | + tal:condition=" |
1957 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
1958 | + LPS.use('lp.registry.structural_subscription', function(Y) { |
1959 | + var module = Y.lp.registry.structural_subscription; |
1960 | + Y.on('domready', function() { |
1961 | + module.setup({content_box: "#structural-subscription-content-box"}); |
1962 | + }); |
1963 | + }); |
1964 | + </script> |
1965 | </metal:block> |
1966 | |
1967 | <tal:heading metal:fill-slot="heading"> |
1968 | @@ -108,12 +119,16 @@ |
1969 | </ul> |
1970 | </div> |
1971 | </div> |
1972 | + <div class="yui-u"> |
1973 | + <div id="structural-subscription-content-box"></div> |
1974 | + </div> |
1975 | </div> |
1976 | </div> |
1977 | |
1978 | <tal:side metal:fill-slot="side" |
1979 | define="overview_menu context/menu:overview"> |
1980 | - <div id="global-actions" class="portlet"> |
1981 | + <div id="global-actions" class="portlet" |
1982 | + condition="overview_menu/subscribe_to_bug_mail/enabled|overview_menu/subscribe/enabled|nothing"> |
1983 | <ul> |
1984 | <li tal:condition="overview_menu/edit/enabled"> |
1985 | <a tal:replace="structure overview_menu/edit/fmt:link" /> |
1986 | @@ -124,9 +139,12 @@ |
1987 | <li tal:condition="overview_menu/reassign/enabled"> |
1988 | <a tal:replace="structure overview_menu/reassign/fmt:link" /> |
1989 | </li> |
1990 | - <li> |
1991 | + <li tal:condition="overview_menu/subscribe/enabled|nothing"> |
1992 | <a tal:replace="structure overview_menu/subscribe/fmt:link" /> |
1993 | </li> |
1994 | + <li tal:condition="overview_menu/subscribe_to_bug_mail/enabled|nothing"> |
1995 | + <a tal:replace="structure overview_menu/subscribe_to_bug_mail/fmt:link" /> |
1996 | + </li> |
1997 | </ul> |
1998 | </div> |
1999 | |
2000 | |
2001 | === modified file 'lib/lp/registry/templates/milestone-index.pt' |
2002 | --- lib/lp/registry/templates/milestone-index.pt 2011-03-04 00:08:20 +0000 |
2003 | +++ lib/lp/registry/templates/milestone-index.pt 2011-03-29 22:36:23 +0000 |
2004 | @@ -6,14 +6,24 @@ |
2005 | metal:use-macro="view/macro:page/main_side" |
2006 | i18n:domain="launchpad"> |
2007 | |
2008 | - <tal:css metal:fill-slot="head_epilogue" |
2009 | + <tal:head-epilogue metal:fill-slot="head_epilogue" |
2010 | condition="view/is_project_milestone"> |
2011 | <style id="hide-side-portlets" type="text/css"> |
2012 | .side { |
2013 | background: #fff; |
2014 | } |
2015 | </style> |
2016 | - </tal:css> |
2017 | + <script type="text/javascript" |
2018 | + tal:condition=" |
2019 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
2020 | + LPS.use('lp.registry.structural_subscription', function(Y) { |
2021 | + var module = Y.lp.registry.structural_subscription; |
2022 | + Y.on('domready', function() { |
2023 | + module.setup({content_box: "#structural-subscription-content-box"}); |
2024 | + }); |
2025 | + }); |
2026 | + </script> |
2027 | + </tal:head-epilogue> |
2028 | |
2029 | <body> |
2030 | <tal:heading metal:fill-slot="heading"> |
2031 | @@ -308,6 +318,9 @@ |
2032 | </li> |
2033 | </ul> |
2034 | </div> |
2035 | + <div class="yui-u"> |
2036 | + <div id="structural-subscription-content-box"></div> |
2037 | + </div> |
2038 | </div> |
2039 | |
2040 | <tal:side metal:fill-slot="side"> |
2041 | |
2042 | === modified file 'lib/lp/registry/templates/product-index.pt' |
2043 | --- lib/lp/registry/templates/product-index.pt 2011-03-23 15:55:44 +0000 |
2044 | +++ lib/lp/registry/templates/product-index.pt 2011-03-29 22:36:23 +0000 |
2045 | @@ -34,9 +34,9 @@ |
2046 | |
2047 | <script type="text/javascript" |
2048 | tal:condition=" |
2049 | - request/features/advanced-structural-subscriptions.enabled"> |
2050 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
2051 | LPS.use('lp.registry.structural_subscription', function(Y) { |
2052 | - module = Y.lp.registry.structural_subscription; |
2053 | + var module = Y.lp.registry.structural_subscription; |
2054 | Y.on('domready', function() { |
2055 | module.setup({content_box: "#structural-subscription-content-box"}); |
2056 | }); |
2057 | |
2058 | === modified file 'lib/lp/registry/templates/productseries-index.pt' |
2059 | --- lib/lp/registry/templates/productseries-index.pt 2011-03-23 16:28:51 +0000 |
2060 | +++ lib/lp/registry/templates/productseries-index.pt 2011-03-29 22:36:23 +0000 |
2061 | @@ -14,6 +14,16 @@ |
2062 | <script id="milestone-script" type="text/javascript" |
2063 | tal:condition="context/menu:overview/create_milestone/enabled" |
2064 | tal:content="view/register_milestone_script"></script> |
2065 | + <script type="text/javascript" |
2066 | + tal:condition=" |
2067 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
2068 | + LPS.use('lp.registry.structural_subscription', function(Y) { |
2069 | + var module = Y.lp.registry.structural_subscription; |
2070 | + Y.on('domready', function() { |
2071 | + module.setup({content_box: "#structural-subscription-content-box"}); |
2072 | + }); |
2073 | + }); |
2074 | + </script> |
2075 | </metal:block> |
2076 | |
2077 | <tal:heading metal:fill-slot="heading"> |
2078 | @@ -178,12 +188,17 @@ |
2079 | </ul> |
2080 | </div> |
2081 | </div> |
2082 | + <div class="yui-u"> |
2083 | + <div id="structural-subscription-content-box"></div> |
2084 | + </div> |
2085 | </div> |
2086 | </div> |
2087 | |
2088 | <tal:side metal:fill-slot="side" |
2089 | - define="overview_menu context/menu:overview"> |
2090 | - <div id="global-actions" class="portlet"> |
2091 | + define="overview_menu context/menu:overview; |
2092 | + feature_flag request/features/malone.advanced-structural-subscriptions.enabled"> |
2093 | + <div id="global-actions" class="portlet" |
2094 | + tal:condition="overview_menu/subscribe_to_bug_mail/enabled|overview_menu/subscribe/enabled"> |
2095 | <ul> |
2096 | <li tal:condition="overview_menu/edit/enabled"> |
2097 | <a tal:replace="structure overview_menu/edit/fmt:link" /> |
2098 | @@ -191,13 +206,21 @@ |
2099 | <li tal:condition="overview_menu/delete/enabled"> |
2100 | <a tal:replace="structure overview_menu/delete/fmt:link" /> |
2101 | </li> |
2102 | - <li> |
2103 | - <a tal:replace="structure overview_menu/subscribe/fmt:link" /> |
2104 | - </li> |
2105 | + <tal:advanced-structural-subscriptions |
2106 | + condition="feature_flag"> |
2107 | + <li tal:condition="overview_menu/subscribe_to_bug_mail/enabled|nothing"> |
2108 | + <a tal:replace="structure overview_menu/subscribe_to_bug_mail/fmt:link" /> |
2109 | + </li> |
2110 | + </tal:advanced-structural-subscriptions> |
2111 | + <tal:not-advanced-structural-subscriptions |
2112 | + condition="not: feature_flag"> |
2113 | + <li tal:condition="overview_menu/subscribe/enabled|nothing"> |
2114 | + <a tal:replace="structure overview_menu/subscribe/fmt:link" /> |
2115 | + </li> |
2116 | + </tal:not-advanced-structural-subscriptions> |
2117 | </ul> |
2118 | </div> |
2119 | |
2120 | - |
2121 | <div id="downloads" class="top-portlet downloads" |
2122 | tal:define="release view/latest_release_with_download_files"> |
2123 | <h2>Downloads</h2> |
2124 | |
2125 | === modified file 'lib/lp/registry/templates/project-index.pt' |
2126 | --- lib/lp/registry/templates/project-index.pt 2010-10-10 21:54:16 +0000 |
2127 | +++ lib/lp/registry/templates/project-index.pt 2011-03-29 22:36:23 +0000 |
2128 | @@ -6,6 +6,22 @@ |
2129 | metal:use-macro="view/macro:page/main_side" |
2130 | i18n:domain="launchpad" |
2131 | > |
2132 | + |
2133 | + <head> |
2134 | + <tal:head-epilogue metal:fill-slot="head_epilogue"> |
2135 | + <script type="text/javascript" |
2136 | + tal:condition=" |
2137 | + request/features/malone.advanced-structural-subscriptions.enabled"> |
2138 | + LPS.use('lp.registry.structural_subscription', function(Y) { |
2139 | + var module = Y.lp.registry.structural_subscription; |
2140 | + Y.on('domready', function() { |
2141 | + module.setup({content_box: "#structural-subscription-content-box"}); |
2142 | + }); |
2143 | + }); |
2144 | + </script> |
2145 | + </tal:head-epilogue> |
2146 | + </head> |
2147 | + |
2148 | <body> |
2149 | <tal:registering metal:fill-slot="registering"> |
2150 | Registered |
2151 | @@ -127,6 +143,9 @@ |
2152 | <tal:sprints content="structure context/@@+portlet-coming-sprints" /> |
2153 | </tal:has-few-project> |
2154 | </div> |
2155 | + <div class="yui-u"> |
2156 | + <div id="structural-subscription-content-box"></div> |
2157 | + </div> |
2158 | </div> |
2159 | </tal:main> |
2160 | |
2161 | |
2162 | === modified file 'lib/lp/services/features/flags.py' |
2163 | --- lib/lp/services/features/flags.py 2011-03-28 05:33:33 +0000 |
2164 | +++ lib/lp/services/features/flags.py 2011-03-29 22:36:23 +0000 |
2165 | @@ -53,6 +53,10 @@ |
2166 | 'boolean', |
2167 | 'Enables advanced bug subscription features.', |
2168 | ''), |
2169 | + ('malone.advanced-structural-subscriptions.enabled', |
2170 | + 'boolean', |
2171 | + 'Enables advanced structural subscriptions', |
2172 | + ''), |
2173 | ('malone.disable_targetnamesearch', |
2174 | 'boolean', |
2175 | 'If true, disables consultation of target names during bug text search.', |
This branch looks good. I just have a few small comments.
I like the substitution of a function for the mix-in.
> ./lib/lp/ bugs/templates/ bugtarget- bugs.pt
> 169: not well-formed (invalid token)
The above can be fixed by replacing the ampersands with "&".
> ./lib/lp/ registry/ browser/ tests/test_ subscription_ links.py
> 295: E301 expected 1 blank line, found 2
> 387: E301 expected 1 blank line, found 2
> 481: E301 expected 1 blank line, found 2
> 567: E301 expected 1 blank line, found 2
These are indeed spurious, but are caused by the commented-out
functions. Looking at the functions it seems that they should be
removed or made to work. I tried a little to get them to work without
luck.
The DistributionSou rcePackageActio nMenu class now has a mutable class
attribute ("links"), which of course is normally a bad thing but it
doesn't appear that it is ever accessed because there is a property of
the same name. I suggest just removing the class attribute altogether.
The snippet
use_ advanced_ features = getFeatureFlag(
'malone. advanced- structural- subscriptions. enabled' ) features:
links. append( 'subscribe_ to_bug_ mail')
links. append( 'subscribe' )
if use_advanced_
else:
is repeated so often I wonder if a helper function named something like link" might not be DRYer.
"add_subscribe_
In these files:
lib/ lp/bugs/ templates/ bug-subscriptio n-list. pt lp/bugs/ templates/ bugtarget- bugs.pt lp/bugs/ templates/ bugtarget- subscription- list.pt lp/registry/ templates/ distribution- index.pt lp/registry/ templates/ distributionsou rcepackage- index.pt lp/registry/ templates/ distroseries- index.pt lp/registry/ templates/ milestone- index.pt lp/registry/ templates/ product- index.pt lp/registry/ templates/ productseries- index.pt
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
I think you want a "var" on the line:
module = Y.lp.registry. structural_ subscription;