Merge lp:~bac/launchpad/accordion-client-2 into lp:launchpad

Proposed by Brad Crittenden
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
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 IStructuralSubscriptionTarget overview and bugs pages.

Description of the change

= Summary =

The new links to subscribe to structural subscriptions are placed on the
overview and bugs facet pages for all IStructuralSubscriptionTargets.
The old structural subscriptions had such a link only on the bugs facet
for those targets.

The new menu link called 'subscribe_to_bug_mail' replaced the older
'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.AnyPerson' which is the least
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_subscription_links

== Demo and Q/A ==

Go to

https://launchpad.dev/firefox
https://bugs.launchpad.dev/firefox

https://launchpad.dev/ubuntu
https://bugs.launchpad.dev/ubuntu

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://launchpad.dev/firefox/+feature-rules

To turn it on enter:

malone.advanced-structural-subscriptions.enabled default 1 on

To turn the flag off enter:

malone.advanced-structural-subscriptions.enabled default 1

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/bugs/templates/bugtarget-portlet-bugfilters.pt
  lib/lp/registry/templates/product-index.pt
  lib/lp/bugs/browser/bugtarget.py
  lib/lp/registry/templates/milestone-index.pt
  lib/lp/registry/templates/distroseries-index.pt
  lib/lp/registry/templates/distribution-index.pt
  lib/lp/registry/templates/distributionsourcepackage-index.pt
  lib/lp/bugs/browser/structuralsubscription.py
  lib/lp/registry/browser/product.py
  lib/lp/registry/browser/distributionsourcepackage.py
  lib/lp/registry/browser/productseries.py
  lib/lp/bugs/templates/buglisting-default.pt
  lib/lp/bugs/browser/bugsubscription.py
  lib/lp/registry/browser/tests/test_product.py
  lib/lp/services/features/flags.py
  lib/lp/bugs/browser/bugtask.py
  lib/lp/bugs/templates/bug-subscription-list.pt
  lib/lp/bugs/templates/bugtarget-bugs.pt
  lib/lp/bugs/templates/bugtarget-subscription-list.pt
  lib/lp/registry/templates/project-index.pt
  lib/lp/registry/javascript/structural-subscription.js
  lib/lp/registry/browser/distribution.py
  lib/lp/registry/browser/distroseries.py
  lib/lp/registry/templates/productseries-index.pt
  lib/lp/registry/browser/milestone.py
  lib/lp/registry/browser/project.py
  lib/lp/registry/browser/tests/test_subscription_links.py

./lib/lp/bugs/templates/bugtarget-bugs.pt
     169: not well-formed (invalid token)
./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

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

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 DistributionSourcePackageActionMenu 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')
    if use_advanced_features:
        links.append('subscribe_to_bug_mail')
    else:
        links.append('subscribe')

is repeated so often I wonder if a helper function named something like
"add_subscribe_link" might not be DRYer.

In these files:

    lib/lp/bugs/templates/bug-subscription-list.pt
    lib/lp/bugs/templates/bugtarget-bugs.pt
    lib/lp/bugs/templates/bugtarget-subscription-list.pt
    lib/lp/registry/templates/distribution-index.pt
    lib/lp/registry/templates/distributionsourcepackage-index.pt
    lib/lp/registry/templates/distroseries-index.pt
    lib/lp/registry/templates/milestone-index.pt
    lib/lp/registry/templates/product-index.pt
    lib/lp/registry/templates/productseries-index.pt

I think you want a "var" on the line:

    module = Y.lp.registry.structural_subscription;

review: Approve (code)
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
--- lib/lp/bugs/browser/bugsubscription.py 2011-03-29 00:11:57 +0000
+++ lib/lp/bugs/browser/bugsubscription.py 2011-03-29 22:36:23 +0000
@@ -40,7 +40,7 @@
40 )40 )
41from lp.bugs.browser.bug import BugViewMixin41from lp.bugs.browser.bug import BugViewMixin
42from lp.bugs.browser.structuralsubscription import (42from lp.bugs.browser.structuralsubscription import (
43 StructuralSubscriptionJSMixin,43 expose_structural_subscription_data_to_js,
44 )44 )
45from lp.bugs.enum import BugNotificationLevel, HIDDEN_BUG_NOTIFICATION_LEVELS45from lp.bugs.enum import BugNotificationLevel, HIDDEN_BUG_NOTIFICATION_LEVELS
46from lp.bugs.interfaces.bugsubscription import IBugSubscription46from lp.bugs.interfaces.bugsubscription import IBugSubscription
@@ -578,9 +578,14 @@
578 return 'subscriber-%s' % self.subscription.person.id578 return 'subscriber-%s' % self.subscription.person.id
579579
580580
581class BugSubscriptionListView(StructuralSubscriptionJSMixin, LaunchpadView):581class BugSubscriptionListView(LaunchpadView):
582 """A view to show all a person's subscriptions to a bug."""582 """A view to show all a person's subscriptions to a bug."""
583583
584 def initialize(self):
585 super(BugSubscriptionListView, self).initialize()
586 expose_structural_subscription_data_to_js(
587 self.context, self.request, self.user, self.subscriptions)
588
584 @property589 @property
585 def subscriptions(self):590 def subscriptions(self):
586 return get_structural_subscriptions_for_bug(591 return get_structural_subscriptions_for_bug(
587592
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py 2011-03-23 19:20:03 +0000
+++ lib/lp/bugs/browser/bugtarget.py 2011-03-29 22:36:23 +0000
@@ -99,7 +99,7 @@
99from lp.bugs.browser.bugrole import BugRoleMixin99from lp.bugs.browser.bugrole import BugRoleMixin
100from lp.bugs.browser.bugtask import BugTaskSearchListingView100from lp.bugs.browser.bugtask import BugTaskSearchListingView
101from lp.bugs.browser.structuralsubscription import (101from lp.bugs.browser.structuralsubscription import (
102 StructuralSubscriptionJSMixin,102 expose_structural_subscription_data_to_js,
103 )103 )
104from lp.bugs.browser.widgets.bug import (104from lp.bugs.browser.widgets.bug import (
105 BugTagsWidget,105 BugTagsWidget,
@@ -1308,10 +1308,12 @@
1308 return 'Bugs in %s' % self.context.title1308 return 'Bugs in %s' % self.context.title
13091309
1310 def initialize(self):1310 def initialize(self):
1311 BugTaskSearchListingView.initialize(self)1311 super(BugTargetBugsView, self).initialize()
1312 bug_statuses_to_show = list(UNRESOLVED_BUGTASK_STATUSES)1312 bug_statuses_to_show = list(UNRESOLVED_BUGTASK_STATUSES)
1313 if IDistroSeries.providedBy(self.context):1313 if IDistroSeries.providedBy(self.context):
1314 bug_statuses_to_show.append(BugTaskStatus.FIXRELEASED)1314 bug_statuses_to_show.append(BugTaskStatus.FIXRELEASED)
1315 expose_structural_subscription_data_to_js(
1316 self.context, self.request, self.user)
13151317
1316 @property1318 @property
1317 def can_have_external_bugtracker(self):1319 def can_have_external_bugtracker(self):
@@ -1565,9 +1567,14 @@
1565 return ProxiedLibraryFileAlias(patch.libraryfile, patch).http_url1567 return ProxiedLibraryFileAlias(patch.libraryfile, patch).http_url
15661568
15671569
1568class TargetSubscriptionView(StructuralSubscriptionJSMixin, LaunchpadView):1570class TargetSubscriptionView(LaunchpadView):
1569 """A view to show all a person's structural subscriptions to a target."""1571 """A view to show all a person's structural subscriptions to a target."""
15701572
1573 def initialize(self):
1574 super(TargetSubscriptionView, self).initialize()
1575 expose_structural_subscription_data_to_js(
1576 self.context, self.request, self.user, self.subscriptions)
1577
1571 @property1578 @property
1572 def subscriptions(self):1579 def subscriptions(self):
1573 return get_structural_subscriptions_for_target(1580 return get_structural_subscriptions_for_target(
15741581
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2011-03-25 15:33:51 +0000
+++ lib/lp/bugs/browser/bugtask.py 2011-03-29 22:36:23 +0000
@@ -195,6 +195,9 @@
195 BugTextView,195 BugTextView,
196 BugViewMixin,196 BugViewMixin,
197 )197 )
198from lp.bugs.browser.structuralsubscription import (
199 expose_structural_subscription_data_to_js,
200 )
198from lp.bugs.browser.bugcomment import (201from lp.bugs.browser.bugcomment import (
199 build_comments_from_chunks,202 build_comments_from_chunks,
200 group_comments_with_activity,203 group_comments_with_activity,
@@ -275,12 +278,11 @@
275from lp.services.fields import PersonChoice278from lp.services.fields import PersonChoice
276from lp.services.propertycache import (279from lp.services.propertycache import (
277 cachedproperty,280 cachedproperty,
278 get_property_cache,
279 )281 )
280282
281283
282DISPLAY_BUG_STATUS_FOR_PATCHES = {284DISPLAY_BUG_STATUS_FOR_PATCHES = {
283 BugTaskStatus.NEW: True,285 BugTaskStatus.NEW: True,
284 BugTaskStatus.INCOMPLETE: True,286 BugTaskStatus.INCOMPLETE: True,
285 BugTaskStatus.INVALID: False,287 BugTaskStatus.INVALID: False,
286 BugTaskStatus.WONTFIX: False,288 BugTaskStatus.WONTFIX: False,
@@ -290,7 +292,7 @@
290 BugTaskStatus.FIXCOMMITTED: True,292 BugTaskStatus.FIXCOMMITTED: True,
291 BugTaskStatus.FIXRELEASED: False,293 BugTaskStatus.FIXRELEASED: False,
292 BugTaskStatus.UNKNOWN: False,294 BugTaskStatus.UNKNOWN: False,
293 BugTaskStatus.EXPIRED: False295 BugTaskStatus.EXPIRED: False,
294 }296 }
295297
296298
@@ -2205,11 +2207,6 @@
2205 return Link(2207 return Link(
2206 '+securitycontact', 'Change security contact', icon='edit')2208 '+securitycontact', 'Change security contact', icon='edit')
22072209
2208 def subscribe(self):
2209 user = getUtility(ILaunchBag).user
2210 if self.context.userCanAlterBugSubscription(user):
2211 return Link('+subscribe', 'Subscribe to bug mail', icon='edit')
2212
2213 def nominations(self):2210 def nominations(self):
2214 return Link('+nominations', 'Review nominations', icon='bug')2211 return Link('+nominations', 'Review nominations', icon='bug')
22152212
@@ -2346,6 +2343,9 @@
2346 # needing validation is already available internally to self.2343 # needing validation is already available internally to self.
2347 self._validate(None, {})2344 self._validate(None, {})
23482345
2346 expose_structural_subscription_data_to_js(
2347 self.context, self.request, self.user)
2348
2349 @property2349 @property
2350 def columns_to_show(self):2350 def columns_to_show(self):
2351 """Returns a sequence of column names to be shown in the listing."""2351 """Returns a sequence of column names to be shown in the listing."""
@@ -3196,7 +3196,7 @@
3196 # Hint to optimize when there are many bugtasks.3196 # Hint to optimize when there are many bugtasks.
3197 view.many_bugtasks = self.many_bugtasks3197 view.many_bugtasks = self.many_bugtasks
3198 return view3198 return view
3199 3199
3200 def getBugTaskAndNominationViews(self):3200 def getBugTaskAndNominationViews(self):
3201 """Return the IBugTasks and IBugNominations views for this bug.3201 """Return the IBugTasks and IBugNominations views for this bug.
32023202
32033203
=== modified file 'lib/lp/bugs/browser/structuralsubscription.py'
--- lib/lp/bugs/browser/structuralsubscription.py 2011-03-24 15:31:53 +0000
+++ lib/lp/bugs/browser/structuralsubscription.py 2011-03-29 22:36:23 +0000
@@ -5,9 +5,9 @@
55
6__all__ = [6__all__ = [
7 'expose_enum_to_js',7 'expose_enum_to_js',
8 'expose_structural_subscription_data_to_js',
8 'expose_user_administered_teams_to_js',9 'expose_user_administered_teams_to_js',
9 'expose_user_subscriptions_to_js',10 'expose_user_subscriptions_to_js',
10 'StructuralSubscriptionJSMixin',
11 'StructuralSubscriptionMenuMixin',11 'StructuralSubscriptionMenuMixin',
12 'StructuralSubscriptionTargetTraversalMixin',12 'StructuralSubscriptionTargetTraversalMixin',
13 'StructuralSubscriptionView',13 'StructuralSubscriptionView',
@@ -33,7 +33,10 @@
33from zope.traversing.browser import absoluteURL33from zope.traversing.browser import absoluteURL
3434
35from canonical.launchpad.webapp.authorization import check_permission35from canonical.launchpad.webapp.authorization import check_permission
36from canonical.launchpad.webapp.menu import Link36from canonical.launchpad.webapp.menu import (
37 enabled_with_permission,
38 Link,
39 )
37from canonical.launchpad.webapp.publisher import (40from canonical.launchpad.webapp.publisher import (
38 canonical_url,41 canonical_url,
39 LaunchpadView,42 LaunchpadView,
@@ -329,6 +332,14 @@
329class StructuralSubscriptionMenuMixin:332class StructuralSubscriptionMenuMixin:
330 """Mix-in class providing the subscription add/edit menu link."""333 """Mix-in class providing the subscription add/edit menu link."""
331334
335 def _getSST(self):
336 if IStructuralSubscriptionTarget.providedBy(self.context):
337 sst = self.context
338 else:
339 # self.context is a view, and the target is its context
340 sst = self.context.context
341 return sst
342
332 def subscribe(self):343 def subscribe(self):
333 """The subscribe menu link.344 """The subscribe menu link.
334345
@@ -337,28 +348,41 @@
337 and displays the edit icon. Otherwise, the link offers to subscribe348 and displays the edit icon. Otherwise, the link offers to subscribe
338 and displays the add icon.349 and displays the add icon.
339 """350 """
340 if IStructuralSubscriptionTarget.providedBy(self.context):351 sst = self._getSST()
341 sst = self.context
342 else:
343 # self.context is a view, and the target is its context
344 sst = self.context.context
345352
346 # ProjectGroup milestones aren't really structural subscription353 # ProjectGroup milestones aren't really structural subscription
347 # targets as they're not real milestones, so you can't subscribe to354 # targets as they're not real milestones, so you can't subscribe to
348 # them.355 # them.
349 enabled = not IProjectGroupMilestone.providedBy(sst)356 enabled = not IProjectGroupMilestone.providedBy(sst)
350
351 if sst.userHasBugSubscriptions(self.user):357 if sst.userHasBugSubscriptions(self.user):
352 text = 'Edit bug mail subscription'358 text = 'Edit bug mail subscription'
353 icon = 'edit'359 icon = 'edit'
354 else:360 else:
355 text = 'Subscribe to bug mail'361 text = 'Subscribe to bug mail'
356 icon = 'add'362 icon = 'add'
357 if enabled == False or (363 if not enabled or (
358 not sst.userCanAlterBugSubscription(self.user, self.user)):364 not sst.userCanAlterBugSubscription(self.user, self.user)):
359 return Link('+subscribe', text, icon=icon, enabled=False)365 enabled = False
360 else:366 return Link('+subscribe', text, icon=icon, enabled=enabled)
361 return Link('+subscribe', text, icon=icon, enabled=enabled)367
368 @enabled_with_permission('launchpad.AnyPerson')
369 def subscribe_to_bug_mail(self):
370 sst = self._getSST()
371 enabled = sst.userCanAlterBugSubscription(self.user, self.user)
372 text = 'Subscribe to bug mail'
373 return Link('#', text, icon='add', hidden=True, enabled=enabled)
374
375
376def expose_structural_subscription_data_to_js(context, request,
377 user, subscriptions=None):
378 """Expose all of the data for a structural subscription to JavaScript."""
379 expose_user_administered_teams_to_js(request, user)
380 expose_enum_to_js(request, BugTaskImportance, 'importances')
381 expose_enum_to_js(request, BugTaskStatus, 'statuses')
382 if subscriptions is None:
383 subscriptions = []
384 expose_user_subscriptions_to_js(
385 user, subscriptions, request)
362386
363387
364def expose_enum_to_js(request, enum, name):388def expose_enum_to_js(request, enum, name):
@@ -424,25 +448,6 @@
424 IJSONRequestCache(request).objects['subscription_info'] = info448 IJSONRequestCache(request).objects['subscription_info'] = info
425449
426450
427class StructuralSubscriptionJSMixin:
428 """A mixin that exposes structural-subscription data in JS.
429
430 Descendants of this mixin must define a `subscriptions` property
431 that returns a list of the subscriptions to cache in the JS of the
432 page.
433 """
434
435 def initialize(self):
436 super(StructuralSubscriptionJSMixin, self).initialize()
437 expose_user_administered_teams_to_js(self.request, self.user)
438 expose_user_subscriptions_to_js(
439 self.user, self.subscriptions, self.request)
440 expose_enum_to_js(self.request, BugTaskImportance, 'importances')
441 expose_enum_to_js(self.request, BugTaskStatus, 'statuses')
442
443 subscriptions = None # Override this.
444
445
446class StructuralSubscribersPortletView(LaunchpadView):451class StructuralSubscribersPortletView(LaunchpadView):
447 """A simple view for displaying the subscribers portlet."""452 """A simple view for displaying the subscribers portlet."""
448453
449454
=== modified file 'lib/lp/bugs/templates/bug-subscription-list.pt'
--- lib/lp/bugs/templates/bug-subscription-list.pt 2011-03-25 21:00:51 +0000
+++ lib/lp/bugs/templates/bug-subscription-list.pt 2011-03-29 22:36:23 +0000
@@ -12,9 +12,11 @@
1212
13<head>13<head>
14 <tal:head-epilogue metal:fill-slot="head_epilogue">14 <tal:head-epilogue metal:fill-slot="head_epilogue">
15 <script type="text/javascript">15 <script type="text/javascript"
16 tal:condition="
17 request/features/malone.advanced-structural-subscriptions.enabled">
16 LPS.use('lp.registry.structural_subscription', function(Y) {18 LPS.use('lp.registry.structural_subscription', function(Y) {
17 module = Y.lp.registry.structural_subscription;19 var module = Y.lp.registry.structural_subscription;
18 Y.on('domready', function() {20 Y.on('domready', function() {
19 module.setup_bug_subscriptions(21 module.setup_bug_subscriptions(
20 {content_box: "#structural-subscription-content-box"})22 {content_box: "#structural-subscription-content-box"})
2123
=== modified file 'lib/lp/bugs/templates/buglisting-default.pt'
--- lib/lp/bugs/templates/buglisting-default.pt 2011-02-24 14:53:05 +0000
+++ lib/lp/bugs/templates/buglisting-default.pt 2011-03-29 22:36:23 +0000
@@ -10,6 +10,16 @@
10<metal:block fill-slot="head_epilogue">10<metal:block fill-slot="head_epilogue">
11 <meta condition="not: view/should_show_bug_information"11 <meta condition="not: view/should_show_bug_information"
12 name="robots" content="noindex,nofollow" />12 name="robots" content="noindex,nofollow" />
13 <script type="text/javascript"
14 tal:condition="
15 request/features/malone.advanced-structural-subscriptions.enabled">
16 LPS.use('lp.registry.structural_subscription', function(Y) {
17 var module = Y.lp.registry.structural_subscription;
18 Y.on('domready', function() {
19 module.setup({content_box: "#structural-subscription-content-box"});
20 });
21 });
22 </script>
13</metal:block>23</metal:block>
1424
15<body>25<body>
@@ -61,6 +71,9 @@
61 use-macro="context/@@+bugtask-macros-tableview/advanced_search_form" />71 use-macro="context/@@+bugtask-macros-tableview/advanced_search_form" />
62 </tal:show_advanced_form>72 </tal:show_advanced_form>
6373
74 <div class="yui-u">
75 <div id="structural-subscription-content-box"></div>
76 </div>
6477
65 </div>78 </div>
66 <div tal:condition="view/bug_tracking_usage/enumvalue:UNKNOWN"79 <div tal:condition="view/bug_tracking_usage/enumvalue:UNKNOWN"
6780
=== modified file 'lib/lp/bugs/templates/bugtarget-bugs.pt'
--- lib/lp/bugs/templates/bugtarget-bugs.pt 2010-12-20 16:01:50 +0000
+++ lib/lp/bugs/templates/bugtarget-bugs.pt 2011-03-29 22:36:23 +0000
@@ -12,11 +12,22 @@
12 <metal:block fill-slot="head_epilogue">12 <metal:block fill-slot="head_epilogue">
13 <meta tal:condition="not: view/bug_tracking_usage/enumvalue:LAUNCHPAD"13 <meta tal:condition="not: view/bug_tracking_usage/enumvalue:LAUNCHPAD"
14 name="robots" content="noindex,nofollow" />14 name="robots" content="noindex,nofollow" />
15 <script type="text/javascript"
16 tal:condition="
17 request/features/malone.advanced-structural-subscriptions.enabled">
18 LPS.use('lp.registry.structural_subscription', function(Y) {
19 var module = Y.lp.registry.structural_subscription;
20 Y.on('domready', function() {
21 module.setup({content_box: "#structural-subscription-content-box"});
22 });
23 });
24 </script>
15 <style type="text/css">25 <style type="text/css">
16 p#more-hot-bugs {float:right; margin-top:7px;}26 p#more-hot-bugs {float:right; margin-top:7px;}
17 </style>27 </style>
18 </metal:block>28</metal:block>
19 <body>29 <body>
30
20 <tal:side metal:fill-slot="side"31 <tal:side metal:fill-slot="side"
21 condition="view/bug_tracking_usage/enumvalue:LAUNCHPAD">32 condition="view/bug_tracking_usage/enumvalue:LAUNCHPAD">
22 <div id="involvement" class="portlet">33 <div id="involvement" class="portlet">
@@ -155,7 +166,7 @@
155 </table>166 </table>
156 <p id="more-hot-bugs"167 <p id="more-hot-bugs"
157 tal:condition="view/hot_bugs_info/has_more_bugs">168 tal:condition="view/hot_bugs_info/has_more_bugs">
158 <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>169 <a tal:attributes="href string:${context/fmt:url/+bugs}?orderby=-heat&amp;field.status%3Alist=NEW&amp;field.status%3Alist=INCOMPLETE_WITH_RESPONSE&amp;field.status%3Alist=INCOMPLETE_WITHOUT_RESPONSE&amp;field.status%3Alist=CONFIRMED&amp;field.status%3Alist=TRIAGED&amp;field.status%3Alist=INPROGRESS&amp;field.status%3Alist=FIXCOMMITTED&amp;field.omit_dupes=on">Show all bugs by heat</a>
159 </p>170 </p>
160 </div>171 </div>
161172
@@ -213,6 +224,10 @@
213 </p>224 </p>
214 </div>225 </div>
215226
227 <div class="yui-u">
228 <div id="structural-subscription-content-box"></div>
229 </div>
230
216 </div><!-- main -->231 </div><!-- main -->
217 </body>232 </body>
218</html>233</html>
219234
=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt'
--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2010-08-16 23:28:48 +0000
+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2011-03-29 22:36:23 +0000
@@ -10,21 +10,46 @@
10 <tbody id="bugfilters-portlet-content"10 <tbody id="bugfilters-portlet-content"
11 tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" />11 tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" />
12 <tbody tal:define="menu context/menu:bugs">12 <tbody tal:define="menu context/menu:bugs">
13 <tr tal:define="subscribe_link menu/subscribe|nothing"13
14 tal:condition="python: subscribe_link and subscribe_link.enabled">14 <tal:advanced-structural-subscriptions
15 <td class="bugs-count" style="padding-top: 1em">15 condition="request/features/malone.advanced-structural-subscriptions.enabled">
16 <a tal:attributes="href subscribe_link/url">16 <tr class="menu-link-subscribe_to_bug_mail invisible-link"
17 <img tal:attributes="src subscribe_link/icon_url" />17 tal:define="subscribe_link menu/subscribe_to_bug_mail|nothing"
18 </a>18 tal:condition="python: subscribe_link and subscribe_link.enabled">
19 </td>19 <td class="bugs-count" style="padding-top: 3px">
20 <td class="bugs-link">20 <a tal:attributes="href subscribe_link/url">
21 <a tal:attributes="href subscribe_link/url"21 <img tal:attributes="src subscribe_link/icon_url" />
22 tal:content="subscribe_link/escapedtext" />22 </a>
23 </td>23 </td>
24 </tr>24 <td class="bugs-link">
25 <a class="js-action"
26 tal:attributes="href subscribe_link/url"
27 tal:content="subscribe_link/escapedtext" />
28 </td>
29 </tr>
30 </tal:advanced-structural-subscriptions>
31
32 <tal:not-advanced-structural-subscriptions
33 condition="not: request/features/malone.advanced-structural-subscriptions.enabled">
34 <tr class="menu-link-subscribe"
35 tal:define="subscribe_link menu/subscribe|nothing"
36 tal:condition="python: subscribe_link and subscribe_link.enabled">
37 <td class="bugs-count" style="padding-top: 3px">
38 <a tal:attributes="href subscribe_link/url">
39 <img tal:attributes="src subscribe_link/icon_url" />
40 </a>
41 </td>
42 <td class="bugs-link">
43 <a tal:attributes="href subscribe_link/url"
44 tal:content="subscribe_link/escapedtext" />
45 </td>
46 </tr>
47 </tal:not-advanced-structural-subscriptions>
48
25 <tr tal:define="review_nominations_link context/menu:bugs/nominations|nothing"49 <tr tal:define="review_nominations_link context/menu:bugs/nominations|nothing"
26 tal:condition="review_nominations_link">50 tal:condition="review_nominations_link"
27 <td class="bugs-count" style="padding-top: 1em">51 style="padding-top: 1em">
52 <td class="bugs-count">
28 <a tal:attributes="href review_nominations_link/url">53 <a tal:attributes="href review_nominations_link/url">
29 <img tal:attributes="src review_nominations_link/icon_url" />54 <img tal:attributes="src review_nominations_link/icon_url" />
30 </a>55 </a>
3156
=== modified file 'lib/lp/bugs/templates/bugtarget-subscription-list.pt'
--- lib/lp/bugs/templates/bugtarget-subscription-list.pt 2011-03-25 21:00:51 +0000
+++ lib/lp/bugs/templates/bugtarget-subscription-list.pt 2011-03-29 22:36:23 +0000
@@ -12,9 +12,11 @@
1212
13<head>13<head>
14 <tal:head-epilogue metal:fill-slot="head_epilogue">14 <tal:head-epilogue metal:fill-slot="head_epilogue">
15 <script type="text/javascript">15 <script type="text/javascript"
16 tal:condition="
17 request/features/malone.advanced-structural-subscriptions.enabled">
16 LPS.use('lp.registry.structural_subscription', function(Y) {18 LPS.use('lp.registry.structural_subscription', function(Y) {
17 module = Y.lp.registry.structural_subscription;19 var module = Y.lp.registry.structural_subscription;
18 Y.on('domready', function() {20 Y.on('domready', function() {
19 module.setup_bug_subscriptions(21 module.setup_bug_subscriptions(
20 {content_box: "#structural-subscription-content-box"})22 {content_box: "#structural-subscription-content-box"})
2123
=== modified file 'lib/lp/registry/browser/__init__.py'
--- lib/lp/registry/browser/__init__.py 2011-02-21 15:22:33 +0000
+++ lib/lp/registry/browser/__init__.py 2011-03-29 22:36:23 +0000
@@ -6,6 +6,7 @@
6__metaclass__ = type6__metaclass__ = type
77
8__all__ = [8__all__ = [
9 'add_subscribe_link',
9 'BaseRdfView',10 'BaseRdfView',
10 'get_status_counts',11 'get_status_counts',
11 'MilestoneOverlayMixin',12 'MilestoneOverlayMixin',
@@ -38,6 +39,7 @@
38 )39 )
39from lp.registry.interfaces.productseries import IProductSeries40from lp.registry.interfaces.productseries import IProductSeries
40from lp.registry.interfaces.series import SeriesStatus41from lp.registry.interfaces.series import SeriesStatus
42from lp.services.features import getFeatureFlag
4143
4244
43class StatusCount:45class StatusCount:
@@ -69,6 +71,16 @@
69 for status in sorted(statuses, key=attrgetter(key))]71 for status in sorted(statuses, key=attrgetter(key))]
7072
7173
74def add_subscribe_link(links):
75 """Based on a feature flag, add the correct link."""
76 use_advanced_features = getFeatureFlag(
77 'malone.advanced-structural-subscriptions.enabled')
78 if use_advanced_features:
79 links.append('subscribe_to_bug_mail')
80 else:
81 links.append('subscribe')
82
83
72class MilestoneOverlayMixin:84class MilestoneOverlayMixin:
73 """A mixin that provides the data for the milestoneoverlay script."""85 """A mixin that provides the data for the milestoneoverlay script."""
7486
7587
=== modified file 'lib/lp/registry/browser/distribution.py'
--- lib/lp/registry/browser/distribution.py 2011-03-04 09:55:17 +0000
+++ lib/lp/registry/browser/distribution.py 2011-03-29 22:36:23 +0000
@@ -83,9 +83,14 @@
83 )83 )
84from lp.bugs.browser.bugtask import BugTargetTraversalMixin84from lp.bugs.browser.bugtask import BugTargetTraversalMixin
85from lp.bugs.browser.structuralsubscription import (85from lp.bugs.browser.structuralsubscription import (
86 expose_structural_subscription_data_to_js,
87 StructuralSubscriptionMenuMixin,
86 StructuralSubscriptionTargetTraversalMixin,88 StructuralSubscriptionTargetTraversalMixin,
87 )89 )
88from lp.registry.browser import RegistryEditFormView90from lp.registry.browser import (
91 add_subscribe_link,
92 RegistryEditFormView,
93 )
89from lp.registry.browser.announcement import HasAnnouncementsView94from lp.registry.browser.announcement import HasAnnouncementsView
90from lp.registry.browser.menu import (95from lp.registry.browser.menu import (
91 IRegistryCollectionNavigationMenu,96 IRegistryCollectionNavigationMenu,
@@ -104,6 +109,7 @@
104 MirrorSpeed,109 MirrorSpeed,
105 )110 )
106from lp.registry.interfaces.series import SeriesStatus111from lp.registry.interfaces.series import SeriesStatus
112from lp.services.features import getFeatureFlag
107from lp.services.geoip.helpers import (113from lp.services.geoip.helpers import (
108 ipaddress_from_request,114 ipaddress_from_request,
109 request_country,115 request_country,
@@ -265,8 +271,8 @@
265 return Link('+unofficialmirrors', text, enabled=enabled, icon='info')271 return Link('+unofficialmirrors', text, enabled=enabled, icon='info')
266272
267273
268class DistributionLinksMixin:274class DistributionLinksMixin(StructuralSubscriptionMenuMixin):
269 """A mixing to provide common links to menus."""275 """A mixin to provide common links to menus."""
270276
271 @enabled_with_permission('launchpad.Edit')277 @enabled_with_permission('launchpad.Edit')
272 def edit(self):278 def edit(self):
@@ -278,7 +284,15 @@
278 """A menu of context actions."""284 """A menu of context actions."""
279 usedfor = IDistribution285 usedfor = IDistribution
280 facet = 'overview'286 facet = 'overview'
281 links = ['edit']287
288 @cachedproperty
289 def links(self):
290 links = ['edit']
291 use_advanced_features = getFeatureFlag(
292 'malone.advanced-structural-subscriptions.enabled')
293 if use_advanced_features:
294 links.append('subscribe_to_bug_mail')
295 return links
282296
283297
284class DistributionOverviewMenu(ApplicationMenu, DistributionLinksMixin):298class DistributionOverviewMenu(ApplicationMenu, DistributionLinksMixin):
@@ -448,13 +462,17 @@
448462
449 usedfor = IDistribution463 usedfor = IDistribution
450 facet = 'bugs'464 facet = 'bugs'
451 links = (465
452 'bugsupervisor',466 @property
453 'securitycontact',467 def links(self):
454 'cve',468 links = [
455 'filebug',469 'bugsupervisor',
456 'subscribe',470 'securitycontact',
457 )471 'cve',
472 'filebug',
473 ]
474 add_subscribe_link(links)
475 return links
458476
459477
460class DistributionSpecificationsMenu(NavigationMenu,478class DistributionSpecificationsMenu(NavigationMenu,
@@ -594,6 +612,11 @@
594class DistributionView(HasAnnouncementsView, FeedsMixin):612class DistributionView(HasAnnouncementsView, FeedsMixin):
595 """Default Distribution view class."""613 """Default Distribution view class."""
596614
615 def initialize(self):
616 super(DistributionView, self).initialize()
617 expose_structural_subscription_data_to_js(
618 self.context, self.request, self.user)
619
597 def linkedMilestonesForSeries(self, series):620 def linkedMilestonesForSeries(self, series):
598 """Return a string of linkified milestones in the series."""621 """Return a string of linkified milestones in the series."""
599 # Listify to remove repeated queries.622 # Listify to remove repeated queries.
600623
=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
--- lib/lp/registry/browser/distributionsourcepackage.py 2011-03-23 16:28:51 +0000
+++ lib/lp/registry/browser/distributionsourcepackage.py 2011-03-29 22:36:23 +0000
@@ -61,9 +61,12 @@
61from lp.app.interfaces.launchpad import IServiceUsage61from lp.app.interfaces.launchpad import IServiceUsage
62from lp.bugs.browser.bugtask import BugTargetTraversalMixin62from lp.bugs.browser.bugtask import BugTargetTraversalMixin
63from lp.bugs.browser.structuralsubscription import (63from lp.bugs.browser.structuralsubscription import (
64 expose_structural_subscription_data_to_js,
65 StructuralSubscriptionMenuMixin,
64 StructuralSubscriptionTargetTraversalMixin,66 StructuralSubscriptionTargetTraversalMixin,
65 )67 )
66from lp.bugs.interfaces.bug import IBugSet68from lp.bugs.interfaces.bug import IBugSet
69from lp.registry.browser import add_subscribe_link
67from lp.registry.browser.pillar import PillarBugsMenu70from lp.registry.browser.pillar import PillarBugsMenu
68from lp.registry.interfaces.distributionsourcepackage import (71from lp.registry.interfaces.distributionsourcepackage import (
69 IDistributionSourcePackage,72 IDistributionSourcePackage,
@@ -123,9 +126,6 @@
123126
124class DistributionSourcePackageLinksMixin:127class DistributionSourcePackageLinksMixin:
125128
126 def subscribe(self):
127 return Link('+subscribe', 'Subscribe to bug mail', icon='edit')
128
129 def publishinghistory(self):129 def publishinghistory(self):
130 return Link('+publishinghistory', 'Show publishing history')130 return Link('+publishinghistory', 'Show publishing history')
131131
@@ -152,17 +152,22 @@
152152
153 usedfor = IDistributionSourcePackage153 usedfor = IDistributionSourcePackage
154 facet = 'overview'154 facet = 'overview'
155 links = [155 links = ['new_bugs', 'open_questions']
156 'subscribe', 'publishinghistory', 'edit', 'new_bugs',
157 'open_questions']
158156
159157
160class DistributionSourcePackageBugsMenu(158class DistributionSourcePackageBugsMenu(
161 PillarBugsMenu, DistributionSourcePackageLinksMixin):159 PillarBugsMenu,
160 StructuralSubscriptionMenuMixin,
161 DistributionSourcePackageLinksMixin):
162162
163 usedfor = IDistributionSourcePackage163 usedfor = IDistributionSourcePackage
164 facet = 'bugs'164 facet = 'bugs'
165 links = ['filebug', 'subscribe']165
166 @cachedproperty
167 def links(self):
168 links = ['filebug']
169 add_subscribe_link(links)
170 return links
166171
167172
168class DistributionSourcePackageNavigation(Navigation,173class DistributionSourcePackageNavigation(Navigation,
@@ -217,12 +222,20 @@
217222
218223
219class DistributionSourcePackageActionMenu(224class DistributionSourcePackageActionMenu(
220 NavigationMenu, DistributionSourcePackageLinksMixin):225 NavigationMenu,
226 StructuralSubscriptionMenuMixin,
227 DistributionSourcePackageLinksMixin):
221 """Action menu for distro source packages."""228 """Action menu for distro source packages."""
222 usedfor = IDistributionSourcePackageActionMenu229 usedfor = IDistributionSourcePackageActionMenu
223 facet = 'overview'230 facet = 'overview'
224 title = 'Actions'231 title = 'Actions'
225 links = ('publishing_history', 'change_log', 'subscribe', 'edit')232
233 @cachedproperty
234 def links(self):
235 links = ['publishing_history', 'change_log']
236 add_subscribe_link(links)
237 links.append('edit')
238 return links
226239
227 def publishing_history(self):240 def publishing_history(self):
228 text = 'View full publishing history'241 text = 'View full publishing history'
@@ -295,6 +308,11 @@
295 """View class for DistributionSourcePackage."""308 """View class for DistributionSourcePackage."""
296 implements(IDistributionSourcePackageActionMenu)309 implements(IDistributionSourcePackageActionMenu)
297310
311 def initialize(self):
312 super(DistributionSourcePackageView, self).initialize()
313 expose_structural_subscription_data_to_js(
314 self.context, self.request, self.user)
315
298 @property316 @property
299 def label(self):317 def label(self):
300 return self.context.title318 return self.context.title
301319
=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py 2011-03-29 18:09:43 +0000
+++ lib/lp/registry/browser/distroseries.py 2011-03-29 22:36:23 +0000
@@ -75,10 +75,14 @@
75 )75 )
76from lp.bugs.browser.bugtask import BugTargetTraversalMixin76from lp.bugs.browser.bugtask import BugTargetTraversalMixin
77from lp.bugs.browser.structuralsubscription import (77from lp.bugs.browser.structuralsubscription import (
78 expose_structural_subscription_data_to_js,
78 StructuralSubscriptionMenuMixin,79 StructuralSubscriptionMenuMixin,
79 StructuralSubscriptionTargetTraversalMixin,80 StructuralSubscriptionTargetTraversalMixin,
80 )81 )
81from lp.registry.browser import MilestoneOverlayMixin82from lp.registry.browser import (
83 add_subscribe_link,
84 MilestoneOverlayMixin,
85 )
82from lp.registry.enum import DistroSeriesDifferenceStatus86from lp.registry.enum import DistroSeriesDifferenceStatus
83from lp.registry.interfaces.distroseries import IDistroSeries87from lp.registry.interfaces.distroseries import IDistroSeries
84from lp.registry.interfaces.distroseriesdifference import (88from lp.registry.interfaces.distroseriesdifference import (
@@ -186,9 +190,23 @@
186190
187 usedfor = IDistroSeries191 usedfor = IDistroSeries
188 facet = 'overview'192 facet = 'overview'
189 links = ['edit', 'reassign', 'driver', 'answers',193
190 'packaging', 'needs_packaging', 'builds', 'queue',194 @property
191 'add_port', 'create_milestone', 'subscribe', 'admin']195 def links(self):
196 links = ['edit',
197 'reassign',
198 'driver',
199 'answers',
200 'packaging',
201 'needs_packaging',
202 'builds',
203 'queue',
204 'add_port',
205 'create_milestone',
206 ]
207 add_subscribe_link(links)
208 links.append('admin')
209 return links
192210
193 @enabled_with_permission('launchpad.Admin')211 @enabled_with_permission('launchpad.Admin')
194 def edit(self):212 def edit(self):
@@ -252,11 +270,14 @@
252270
253 usedfor = IDistroSeries271 usedfor = IDistroSeries
254 facet = 'bugs'272 facet = 'bugs'
255 links = (273
256 'cve',274 @property
257 'nominations',275 def links(self):
258 'subscribe',276 links = ['cve',
259 )277 'nominations',
278 ]
279 add_subscribe_link(links)
280 return links
260281
261 def cve(self):282 def cve(self):
262 return Link('+cve', 'CVE reports', icon='cve')283 return Link('+cve', 'CVE reports', icon='cve')
@@ -327,12 +348,15 @@
327 self.context.datereleased = UTC_NOW348 self.context.datereleased = UTC_NOW
328349
329350
330class DistroSeriesView(MilestoneOverlayMixin):351class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin):
331352
332 def initialize(self):353 def initialize(self):
354 super(DistroSeriesView, self).initialize()
333 self.displayname = '%s %s' % (355 self.displayname = '%s %s' % (
334 self.context.distribution.displayname,356 self.context.distribution.displayname,
335 self.context.version)357 self.context.version)
358 expose_structural_subscription_data_to_js(
359 self.context, self.request, self.user)
336360
337 @property361 @property
338 def page_title(self):362 def page_title(self):
339363
=== modified file 'lib/lp/registry/browser/milestone.py'
--- lib/lp/registry/browser/milestone.py 2011-03-04 00:55:49 +0000
+++ lib/lp/registry/browser/milestone.py 2011-03-29 22:36:23 +0000
@@ -64,6 +64,7 @@
64 get_status_counts,64 get_status_counts,
65 RegistryDeleteViewMixin,65 RegistryDeleteViewMixin,
66 )66 )
67from lp.registry.browser import add_subscribe_link
67from lp.registry.browser.product import ProductDownloadFileMixin68from lp.registry.browser.product import ProductDownloadFileMixin
68from lp.registry.interfaces.distroseries import IDistroSeries69from lp.registry.interfaces.distroseries import IDistroSeries
69from lp.registry.interfaces.milestone import (70from lp.registry.interfaces.milestone import (
@@ -140,14 +141,25 @@
140class MilestoneContextMenu(ContextMenu, MilestoneLinkMixin):141class MilestoneContextMenu(ContextMenu, MilestoneLinkMixin):
141 """The menu for this milestone."""142 """The menu for this milestone."""
142 usedfor = IMilestone143 usedfor = IMilestone
143 links = ['edit', 'subscribe', 'create_release']144
145 @cachedproperty
146 def links(self):
147 links = ['edit']
148 add_subscribe_link(links)
149 links.append('create_release')
150 return links
144151
145152
146class MilestoneOverviewNavigationMenu(NavigationMenu, MilestoneLinkMixin):153class MilestoneOverviewNavigationMenu(NavigationMenu, MilestoneLinkMixin):
147 """Overview navigation menu for `IMilestone` objects."""154 """Overview navigation menu for `IMilestone` objects."""
148 usedfor = IMilestone155 usedfor = IMilestone
149 facet = 'overview'156 facet = 'overview'
150 links = ('edit', 'delete', 'subscribe')157
158 @cachedproperty
159 def links(self):
160 links = ['edit', 'delete']
161 add_subscribe_link(links)
162 return links
151163
152164
153class MilestoneOverviewMenu(ApplicationMenu, MilestoneLinkMixin):165class MilestoneOverviewMenu(ApplicationMenu, MilestoneLinkMixin):
154166
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2011-03-23 15:55:44 +0000
+++ lib/lp/registry/browser/product.py 2011-03-29 22:36:23 +0000
@@ -153,14 +153,13 @@
153 BugTargetTraversalMixin,153 BugTargetTraversalMixin,
154 get_buglisting_search_filter_url,154 get_buglisting_search_filter_url,
155 )155 )
156from lp.bugs.interfaces.bugtask import (156from lp.bugs.interfaces.bugtask import RESOLVED_BUGTASK_STATUSES
157 RESOLVED_BUGTASK_STATUSES,
158 BugTaskImportance,
159 BugTaskStatus,
160 )
161from lp.code.browser.branchref import BranchRef157from lp.code.browser.branchref import BranchRef
162from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin158from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
163from lp.registry.browser import BaseRdfView159from lp.registry.browser import (
160 add_subscribe_link,
161 BaseRdfView,
162 )
164from lp.registry.browser.announcement import HasAnnouncementsView163from lp.registry.browser.announcement import HasAnnouncementsView
165from lp.registry.browser.branding import BrandingChangeView164from lp.registry.browser.branding import BrandingChangeView
166from lp.registry.browser.menu import (165from lp.registry.browser.menu import (
@@ -173,8 +172,7 @@
173 )172 )
174from lp.registry.browser.productseries import get_series_branch_error173from lp.registry.browser.productseries import get_series_branch_error
175from lp.bugs.browser.structuralsubscription import (174from lp.bugs.browser.structuralsubscription import (
176 expose_enum_to_js,175 expose_structural_subscription_data_to_js,
177 expose_user_administered_teams_to_js,
178 StructuralSubscriptionMenuMixin,176 StructuralSubscriptionMenuMixin,
179 StructuralSubscriptionTargetTraversalMixin,177 StructuralSubscriptionTargetTraversalMixin,
180 )178 )
@@ -193,7 +191,6 @@
193from lp.registry.interfaces.productseries import IProductSeries191from lp.registry.interfaces.productseries import IProductSeries
194from lp.registry.interfaces.series import SeriesStatus192from lp.registry.interfaces.series import SeriesStatus
195from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet193from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
196from lp.services import features
197from lp.services.fields import (194from lp.services.fields import (
198 PillarAliases,195 PillarAliases,
199 PublicPersonChoice,196 PublicPersonChoice,
@@ -585,22 +582,12 @@
585 facet = 'overview'582 facet = 'overview'
586 title = 'Actions'583 title = 'Actions'
587584
588 @property585 @cachedproperty
589 def links(self):586 def links(self):
590 links = ['edit', 'review_license', 'administer']587 links = ['edit', 'review_license', 'administer']
591 use_advanced_features = features.getFeatureFlag(588 add_subscribe_link(links)
592 'advanced-structural-subscriptions.enabled')
593 if use_advanced_features:
594 links.append('subscribe_to_bug_mail')
595 else:
596 links.append('subscribe')
597 return links589 return links
598590
599 @enabled_with_permission('launchpad.AnyPerson')
600 def subscribe_to_bug_mail(self):
601 text = 'Subscribe to bug mail'
602 return Link('#', text, icon='add', hidden=True)
603
604591
605class ProductOverviewMenu(ApplicationMenu, ProductEditLinksMixin,592class ProductOverviewMenu(ApplicationMenu, ProductEditLinksMixin,
606 HasRecipesMenuMixin):593 HasRecipesMenuMixin):
@@ -694,16 +681,20 @@
694681
695 usedfor = IProduct682 usedfor = IProduct
696 facet = 'bugs'683 facet = 'bugs'
697 links = (
698 'filebug',
699 'bugsupervisor',
700 'securitycontact',
701 'cve',
702 'subscribe',
703 'configure_bugtracker',
704 )
705 configurable_bugtracker = True684 configurable_bugtracker = True
706685
686 @cachedproperty
687 def links(self):
688 links = [
689 'filebug',
690 'bugsupervisor',
691 'securitycontact',
692 'cve',
693 ]
694 add_subscribe_link(links)
695 links.append('configure_bugtracker')
696 return links
697
707698
708class ProductSpecificationsMenu(NavigationMenu, ProductEditLinksMixin,699class ProductSpecificationsMenu(NavigationMenu, ProductEditLinksMixin,
709 HasSpecificationsMenuMixin):700 HasSpecificationsMenuMixin):
@@ -1009,6 +1000,7 @@
1009 self.form = request.form_ng1000 self.form = request.form_ng
10101001
1011 def initialize(self):1002 def initialize(self):
1003 super(ProductView, self).initialize()
1012 self.status_message = None1004 self.status_message = None
1013 product = self.context1005 product = self.context
1014 title_field = IProduct['title']1006 title_field = IProduct['title']
@@ -1028,9 +1020,8 @@
1028 self.show_programming_languages = bool(1020 self.show_programming_languages = bool(
1029 self.context.programminglang or1021 self.context.programminglang or
1030 check_permission('launchpad.Edit', self.context))1022 check_permission('launchpad.Edit', self.context))
1031 expose_user_administered_teams_to_js(self.request, self.user)1023 expose_structural_subscription_data_to_js(
1032 expose_enum_to_js(self.request, BugTaskImportance, 'importances')1024 self.context, self.request, self.user)
1033 expose_enum_to_js(self.request, BugTaskStatus, 'statuses')
10341025
1035 @property1026 @property
1036 def show_license_status(self):1027 def show_license_status(self):
10371028
=== modified file 'lib/lp/registry/browser/productseries.py'
--- lib/lp/registry/browser/productseries.py 2011-02-03 10:35:36 +0000
+++ lib/lp/registry/browser/productseries.py 2011-03-29 22:36:23 +0000
@@ -118,6 +118,7 @@
118 ICodeImportSet,118 ICodeImportSet,
119 )119 )
120from lp.registry.browser import (120from lp.registry.browser import (
121 add_subscribe_link,
121 BaseRdfView,122 BaseRdfView,
122 MilestoneOverlayMixin,123 MilestoneOverlayMixin,
123 RegistryDeleteViewMixin,124 RegistryDeleteViewMixin,
@@ -128,6 +129,7 @@
128 PillarView,129 PillarView,
129 )130 )
130from lp.bugs.browser.structuralsubscription import (131from lp.bugs.browser.structuralsubscription import (
132 expose_structural_subscription_data_to_js,
131 StructuralSubscriptionMenuMixin,133 StructuralSubscriptionMenuMixin,
132 StructuralSubscriptionTargetTraversalMixin,134 StructuralSubscriptionTargetTraversalMixin,
133 )135 )
@@ -279,19 +281,23 @@
279 """The overview menu."""281 """The overview menu."""
280 usedfor = IProductSeries282 usedfor = IProductSeries
281 facet = 'overview'283 facet = 'overview'
282 links = [284
283 'configure_bugtracker',285 @cachedproperty
284 'create_milestone',286 def links(self):
285 'create_release',287 links = [
286 'delete',288 'configure_bugtracker',
287 'driver',289 'create_milestone',
288 'edit',290 'create_release',
289 'link_branch',291 'delete',
290 'rdf',292 'driver',
291 'set_branch',293 'edit',
292 'subscribe',294 'link_branch',
293 'ubuntupkg',295 'rdf',
294 ]296 'set_branch',
297 ]
298 add_subscribe_link(links)
299 links.append('ubuntupkg')
300 return links
295301
296 @enabled_with_permission('launchpad.Edit')302 @enabled_with_permission('launchpad.Edit')
297 def configure_bugtracker(self):303 def configure_bugtracker(self):
@@ -380,11 +386,12 @@
380 """The bugs menu."""386 """The bugs menu."""
381 usedfor = IProductSeries387 usedfor = IProductSeries
382 facet = 'bugs'388 facet = 'bugs'
383 links = (389
384 'new',390 @cachedproperty
385 'nominations',391 def links(self):
386 'subscribe',392 links = ['new', 'nominations']
387 )393 add_subscribe_link(links)
394 return links
388395
389 def new(self):396 def new(self):
390 """Return a link to report a bug in this series."""397 """Return a link to report a bug in this series."""
@@ -439,6 +446,11 @@
439class ProductSeriesView(LaunchpadView, MilestoneOverlayMixin):446class ProductSeriesView(LaunchpadView, MilestoneOverlayMixin):
440 """A view to show a series with translations."""447 """A view to show a series with translations."""
441448
449 def initialize(self):
450 super(ProductSeriesView, self).initialize()
451 expose_structural_subscription_data_to_js(
452 self.context, self.request, self.user)
453
442 @property454 @property
443 def page_title(self):455 def page_title(self):
444 """Return the HTML page title."""456 """Return the HTML page title."""
445457
=== modified file 'lib/lp/registry/browser/project.py'
--- lib/lp/registry/browser/project.py 2011-01-21 08:30:55 +0000
+++ lib/lp/registry/browser/project.py 2011-03-29 22:36:23 +0000
@@ -74,7 +74,10 @@
74from lp.blueprints.browser.specificationtarget import (74from lp.blueprints.browser.specificationtarget import (
75 HasSpecificationsMenuMixin,75 HasSpecificationsMenuMixin,
76 )76 )
77from lp.registry.browser import BaseRdfView77from lp.registry.browser import (
78 add_subscribe_link,
79 BaseRdfView,
80 )
78from lp.registry.browser.announcement import HasAnnouncementsView81from lp.registry.browser.announcement import HasAnnouncementsView
79from lp.registry.browser.branding import BrandingChangeView82from lp.registry.browser.branding import BrandingChangeView
80from lp.registry.browser.menu import (83from lp.registry.browser.menu import (
@@ -88,6 +91,8 @@
88 ProjectAddStepTwo,91 ProjectAddStepTwo,
89 )92 )
90from lp.bugs.browser.structuralsubscription import (93from lp.bugs.browser.structuralsubscription import (
94 expose_structural_subscription_data_to_js,
95 StructuralSubscriptionMenuMixin,
91 StructuralSubscriptionTargetTraversalMixin,96 StructuralSubscriptionTargetTraversalMixin,
92 )97 )
93from lp.registry.interfaces.product import IProductSet98from lp.registry.interfaces.product import IProductSet
@@ -270,20 +275,20 @@
270 """Marker interface for views that use ProjectActionMenu."""275 """Marker interface for views that use ProjectActionMenu."""
271276
272277
273class ProjectActionMenu(ProjectAdminMenuMixin, NavigationMenu):278class ProjectActionMenu(ProjectAdminMenuMixin,
279 StructuralSubscriptionMenuMixin,
280 NavigationMenu):
274281
275 usedfor = IProjectGroupActionMenu282 usedfor = IProjectGroupActionMenu
276 facet = 'overview'283 facet = 'overview'
277 title = 'Action menu'284 title = 'Action menu'
278 links = ('subscribe', 'edit', 'administer')
279285
280 # XXX: salgado, bug=412178, 2009-08-10: This should be shown in the +index286 @cachedproperty
281 # page of the project's bugs facet, but that would require too much work287 def links(self):
282 # and I just want to convert this page to 3.0, so I'll leave it here for288 links = []
283 # now.289 add_subscribe_link(links)
284 def subscribe(self):290 links.extend(['edit', 'administer'])
285 text = 'Subscribe to bug mail'291 return links
286 return Link('+subscribe', text, icon='edit')
287292
288 @enabled_with_permission('launchpad.Edit')293 @enabled_with_permission('launchpad.Edit')
289 def edit(self):294 def edit(self):
@@ -323,24 +328,31 @@
323 return Link('+addquestion', text, icon='add')328 return Link('+addquestion', text, icon='add')
324329
325330
326class ProjectBugsMenu(ApplicationMenu):331class ProjectBugsMenu(StructuralSubscriptionMenuMixin,
332 ApplicationMenu):
327333
328 usedfor = IProjectGroup334 usedfor = IProjectGroup
329 facet = 'bugs'335 facet = 'bugs'
330 links = ['new', 'subscribe']336
337 @cachedproperty
338 def links(self):
339 links = ['new']
340 add_subscribe_link(links)
341 return links
331342
332 def new(self):343 def new(self):
333 text = 'Report a Bug'344 text = 'Report a Bug'
334 return Link('+filebug', text, icon='add')345 return Link('+filebug', text, icon='add')
335346
336 def subscribe(self):
337 text = 'Subscribe to bug mail'
338 return Link('+subscribe', text, icon='edit')
339
340347
341class ProjectView(HasAnnouncementsView, FeedsMixin):348class ProjectView(HasAnnouncementsView, FeedsMixin):
342 implements(IProjectGroupActionMenu)349 implements(IProjectGroupActionMenu)
343350
351 def initialize(self):
352 super(ProjectView, self).initialize()
353 expose_structural_subscription_data_to_js(
354 self.context, self.request, self.user)
355
344 @cachedproperty356 @cachedproperty
345 def has_many_projects(self):357 def has_many_projects(self):
346 """Does the projectgroup have many sub projects.358 """Does the projectgroup have many sub projects.
347359
=== modified file 'lib/lp/registry/browser/tests/test_product.py'
--- lib/lp/registry/browser/tests/test_product.py 2011-03-07 19:53:40 +0000
+++ lib/lp/registry/browser/tests/test_product.py 2011-03-29 22:36:23 +0000
@@ -13,10 +13,14 @@
13from zope.security.proxy import removeSecurityProxy13from zope.security.proxy import removeSecurityProxy
1414
15from canonical.config import config15from canonical.config import config
16from canonical.launchpad.testing.pages import find_tag_by_id16from canonical.launchpad.testing.pages import (
17 find_tag_by_id,
18 )
17from canonical.testing.layers import DatabaseFunctionalLayer19from canonical.testing.layers import DatabaseFunctionalLayer
18from lp.app.enums import ServiceUsage20from lp.app.enums import ServiceUsage
19from lp.registry.browser.product import ProductLicenseMixin21from lp.registry.browser.product import (
22 ProductLicenseMixin,
23 )
20from lp.registry.interfaces.product import (24from lp.registry.interfaces.product import (
21 License,25 License,
22 IProductSet,26 IProductSet,
2327
=== added file 'lib/lp/registry/browser/tests/test_subscription_links.py'
--- lib/lp/registry/browser/tests/test_subscription_links.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_subscription_links.py 2011-03-29 22:36:23 +0000
@@ -0,0 +1,609 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for subscription links."""
5
6__metaclass__ = type
7
8import unittest
9from zope.component import getUtility
10from BeautifulSoup import BeautifulSoup
11
12from canonical.launchpad.webapp.interaction import ANONYMOUS
13from canonical.launchpad.webapp.interfaces import ILaunchBag
14from canonical.launchpad.webapp.publisher import canonical_url
15from canonical.launchpad.webapp.servers import LaunchpadTestRequest
16from canonical.launchpad.testing.pages import (
17 first_tag_by_class,
18 )
19from canonical.testing.layers import DatabaseFunctionalLayer
20
21from lp.registry.interfaces.person import IPersonSet
22from lp.services.features import (
23 get_relevant_feature_controller,
24 )
25from lp.services.features.testing import FeatureFixture
26from lp.testing import (
27 celebrity_logged_in,
28 person_logged_in,
29 BrowserTestCase,
30 TestCaseWithFactory,
31 )
32from lp.testing.sampledata import ADMIN_EMAIL
33from lp.testing.views import (
34 create_initialized_view,
35 )
36
37
38class _TestStructSubs(TestCaseWithFactory):
39 """Test structural subscriptions base class.
40
41 The link to structural subscriptions is controlled by the feature flag
42 'malone.advanced-structural-subscriptions.enabled'. If it is false, the
43 old link leading to +subscribe is shown. If it is true then the new
44 JavaScript control is used.
45 """
46
47 layer = DatabaseFunctionalLayer
48 feature_flag = 'malone.advanced-structural-subscriptions.enabled'
49
50 def setUp(self):
51 super(_TestStructSubs, self).setUp()
52 self.regular_user = self.factory.makePerson()
53
54 def _create_scenario(self, user, flag):
55 with person_logged_in(user):
56 with FeatureFixture({self.feature_flag: flag}):
57 view = self.create_view(user)
58 self.contents = view.render()
59 old_link = first_tag_by_class(
60 self.contents, 'menu-link-subscribe')
61 new_link = first_tag_by_class(
62 self.contents, 'menu-link-subscribe_to_bug_mail')
63 return old_link, new_link
64
65 def create_view(self, user):
66 request = LaunchpadTestRequest(
67 PATH_INFO='/', HTTP_COOKIE='', QUERY_STRING='')
68 request.features = get_relevant_feature_controller()
69 return create_initialized_view(
70 self.target, self.view, principal=user,
71 rootsite=self.rootsite,
72 request=request, current_request=False)
73
74 def test_subscribe_link_feature_flag_off_owner(self):
75 old_link, new_link = self._create_scenario(
76 self.target.owner, None)
77 self.assertNotEqual(None, old_link, self.contents)
78 self.assertEqual(None, new_link, self.contents)
79
80 def test_subscribe_link_feature_flag_on_owner(self):
81 # Test the new subscription link.
82 old_link, new_link = self._create_scenario(
83 self.target.owner, 'on')
84 self.assertEqual(None, old_link, self.contents)
85 self.assertNotEqual(None, new_link, self.contents)
86
87 def test_subscribe_link_feature_flag_off_user(self):
88 old_link, new_link = self._create_scenario(
89 self.regular_user, None)
90 self.assertNotEqual(None, old_link, self.contents)
91 self.assertEqual(None, new_link, self.contents)
92
93 def test_subscribe_link_feature_flag_on_user(self):
94 old_link, new_link = self._create_scenario(
95 self.regular_user, 'on')
96 self.assertEqual(None, old_link, self.contents)
97 self.assertNotEqual(None, new_link, self.contents)
98
99 def test_subscribe_link_feature_flag_off_anonymous(self):
100 old_link, new_link = self._create_scenario(
101 ANONYMOUS, None)
102 # The old subscribe link is actually shown to anonymous users but the
103 # behavior has changed with the new link.
104 self.assertNotEqual(None, old_link, self.contents)
105 self.assertEqual(None, new_link, self.contents)
106
107 def test_subscribe_link_feature_flag_on_anonymous(self):
108 old_link, new_link = self._create_scenario(
109 ANONYMOUS, 'on')
110 # The subscribe link is not shown to anonymous.
111 self.assertEqual(None, old_link, self.contents)
112 self.assertEqual(None, new_link, self.contents)
113
114
115class TestProductViewStructSubs(_TestStructSubs):
116 """Test structural subscriptions on the product view."""
117
118 rootsite = None
119 view = '+index'
120
121 def setUp(self):
122 super(TestProductViewStructSubs, self).setUp()
123 self.target = self.factory.makeProduct(official_malone=True)
124
125
126class TestProductBugsStructSubs(TestProductViewStructSubs):
127 """Test structural subscriptions on the product bugs view."""
128
129 rootsite = 'bugs'
130 view = '+bugs-index'
131
132
133class TestProjectGroupViewStructSubs(_TestStructSubs):
134 """Test structural subscriptions on the project group view."""
135
136 rootsite = None
137 view = '+index'
138
139 def setUp(self):
140 super(TestProjectGroupViewStructSubs, self).setUp()
141 self.target = self.factory.makeProject()
142 self.factory.makeProduct(
143 project=self.target, official_malone=True)
144
145
146class TestProjectGroupBugsStructSubs(TestProjectGroupViewStructSubs):
147 """Test structural subscriptions on the project group bugs view."""
148
149 rootsite = 'bugs'
150 view = '+bugs'
151
152
153class TestProductSeriesViewStructSubs(_TestStructSubs):
154 """Test structural subscriptions on the product series view."""
155
156 rootsite = None
157 view = '+index'
158
159 def setUp(self):
160 super(TestProductSeriesViewStructSubs, self).setUp()
161 self.target = self.factory.makeProductSeries()
162
163
164class TestProductSeriesBugsStructSubs(TestProductSeriesViewStructSubs):
165 """Test structural subscriptions on the product series bugs view."""
166
167 rootsite = 'bugs'
168 view = '+bugs-index'
169
170 def setUp(self):
171 super(TestProductSeriesBugsStructSubs, self).setUp()
172 with person_logged_in(self.target.product.owner):
173 self.target.product.official_malone = True
174
175
176class TestDistributionSourcePackageViewStructSubs(_TestStructSubs):
177 """Test structural subscriptions on the distro src pkg view."""
178
179 rootsite = None
180 view = '+index'
181
182 def setUp(self):
183 super(TestDistributionSourcePackageViewStructSubs, self).setUp()
184 distro = self.factory.makeDistribution()
185 with person_logged_in(distro.owner):
186 distro.official_malone = True
187 self.target = self.factory.makeDistributionSourcePackage(
188 distribution=distro)
189 self.regular_user = self.factory.makePerson()
190
191 # DistributionSourcePackages do not have owners.
192 test_subscribe_link_feature_flag_off_owner = None
193 test_subscribe_link_feature_flag_on_owner = None
194
195
196class TestDistributionSourcePackageBugsStructSubs(
197 TestDistributionSourcePackageViewStructSubs):
198 """Test structural subscriptions on the distro src pkg bugs view."""
199
200 rootsite = 'bugs'
201 view = '+bugs'
202
203
204class TestDistroViewStructSubs(BrowserTestCase):
205 """Test structural subscriptions on the distribution view.
206
207 Distributions are special. They are IStructuralSubscriptionTargets but
208 have complicated rules to ensure Ubuntu users don't subscribe and become
209 overwhelmed with email. If a distro does not have a bug supervisor set,
210 then anyone can create a structural subscription for themselves. If the
211 bug supervisor is set, then only people in the bug supervisor team can
212 subscribe themselves. Admins can subscribe anyone.
213 """
214
215 layer = DatabaseFunctionalLayer
216 feature_flag = 'malone.advanced-structural-subscriptions.enabled'
217 rootsite = None
218 view = '+index'
219
220 def setUp(self):
221 super(TestDistroViewStructSubs, self).setUp()
222 self.target = self.factory.makeDistribution()
223 with person_logged_in(self.target.owner):
224 self.target.official_malone = True
225 self.regular_user = self.factory.makePerson()
226
227 def _create_scenario(self, user, flag):
228 with person_logged_in(user):
229 with FeatureFixture({self.feature_flag: flag}):
230 logged_in_user = getUtility(ILaunchBag).user
231 no_login = logged_in_user is None
232 browser = self.getViewBrowser(
233 self.target, view_name=self.view,
234 rootsite=self.rootsite,
235 no_login=no_login,
236 user=logged_in_user)
237 self.contents = browser.contents
238 soup = BeautifulSoup(self.contents)
239 href = canonical_url(
240 self.target, rootsite=self.rootsite,
241 view_name='+subscribe')
242 old_link = soup.find('a', href=href)
243 new_link = first_tag_by_class(
244 self.contents, 'menu-link-subscribe_to_bug_mail')
245 return old_link, new_link
246
247 def test_subscribe_link_feature_flag_off_owner(self):
248 old_link, new_link = self._create_scenario(
249 self.target.owner, None)
250 self.assertEqual(None, old_link, self.contents)
251 self.assertEqual(None, new_link, self.contents)
252
253 def test_subscribe_link_feature_flag_on_owner(self):
254 old_link, new_link = self._create_scenario(
255 self.target.owner, 'on')
256 self.assertEqual(None, old_link, self.contents)
257 self.assertNotEqual(None, new_link, self.contents)
258
259 def test_subscribe_link_feature_flag_off_user(self):
260 old_link, new_link = self._create_scenario(
261 self.regular_user, None)
262 self.assertEqual(None, old_link, self.contents)
263 self.assertEqual(None, new_link, self.contents)
264
265 def test_subscribe_link_feature_flag_on_user_no_bug_super(self):
266 old_link, new_link = self._create_scenario(
267 self.regular_user, 'on')
268 self.assertEqual(None, old_link, self.contents)
269 self.assertNotEqual(None, new_link, self.contents)
270
271 def test_subscribe_link_feature_flag_on_user_with_bug_super(self):
272 with celebrity_logged_in('admin'):
273 admin = getUtility(ILaunchBag).user
274 supervisor = self.factory.makePerson()
275 self.target.setBugSupervisor(
276 supervisor, admin)
277 old_link, new_link = self._create_scenario(
278 self.regular_user, 'on')
279 self.assertEqual(None, old_link, self.contents)
280 self.assertEqual(None, new_link, self.contents)
281
282 def test_subscribe_link_feature_flag_off_anonymous(self):
283 old_link, new_link = self._create_scenario(
284 ANONYMOUS, None)
285 self.assertEqual(None, old_link, self.contents)
286 self.assertEqual(None, new_link, self.contents)
287
288 def test_subscribe_link_feature_flag_on_anonymous(self):
289 old_link, new_link = self._create_scenario(
290 ANONYMOUS, 'on')
291 self.assertEqual(None, old_link, self.contents)
292 self.assertEqual(None, new_link, self.contents)
293
294 def test_subscribe_link_feature_flag_off_bug_super(self):
295 with celebrity_logged_in('admin'):
296 admin = getUtility(ILaunchBag).user
297 self.target.setBugSupervisor(
298 self.regular_user, admin)
299 old_link, new_link = self._create_scenario(
300 self.regular_user, None)
301 self.assertEqual(None, old_link, self.contents)
302 self.assertEqual(None, new_link, self.contents)
303
304 def test_subscribe_link_feature_flag_on_bug_super(self):
305 with celebrity_logged_in('admin'):
306 admin = getUtility(ILaunchBag).user
307 self.target.setBugSupervisor(
308 self.regular_user, admin)
309 old_link, new_link = self._create_scenario(
310 self.regular_user, 'on')
311 self.assertEqual(None, old_link, self.contents)
312 self.assertNotEqual(None, new_link, self.contents)
313
314 def test_subscribe_link_feature_flag_off_admin(self):
315 admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
316 old_link, new_link = self._create_scenario(
317 admin, None)
318 self.assertEqual(None, old_link, self.contents)
319 self.assertEqual(None, new_link, self.contents)
320
321 def test_subscribe_link_feature_flag_on_admin(self):
322 from lp.testing.sampledata import ADMIN_EMAIL
323 admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
324 old_link, new_link = self._create_scenario(
325 admin, 'on')
326 self.assertEqual(None, old_link, self.contents)
327 self.assertNotEqual(None, new_link, self.contents)
328
329
330class TestDistroBugsStructSubs(TestDistroViewStructSubs):
331 """Test structural subscriptions on the distro bugs view."""
332
333 rootsite = 'bugs'
334 view = '+bugs-index'
335
336 def test_subscribe_link_feature_flag_off_owner(self):
337 old_link, new_link = self._create_scenario(
338 self.target.owner, None)
339 self.assertNotEqual(None, old_link, self.contents)
340 self.assertEqual(None, new_link, self.contents)
341
342 def test_subscribe_link_feature_flag_on_owner(self):
343 old_link, new_link = self._create_scenario(
344 self.target.owner, 'on')
345 self.assertEqual(None, old_link, self.contents)
346 self.assertNotEqual(None, new_link, self.contents)
347
348 def test_subscribe_link_feature_flag_off_user(self):
349 old_link, new_link = self._create_scenario(
350 self.regular_user, None)
351 self.assertNotEqual(None, old_link, self.contents)
352 self.assertEqual(None, new_link, self.contents)
353
354 def test_subscribe_link_feature_flag_on_user_no_bug_super(self):
355 old_link, new_link = self._create_scenario(
356 self.regular_user, 'on')
357 self.assertEqual(None, old_link, self.contents)
358 self.assertNotEqual(None, new_link, self.contents)
359
360 def test_subscribe_link_feature_flag_on_user_with_bug_super(self):
361 with celebrity_logged_in('admin'):
362 admin = getUtility(ILaunchBag).user
363 supervisor = self.factory.makePerson()
364 self.target.setBugSupervisor(
365 supervisor, admin)
366 old_link, new_link = self._create_scenario(
367 self.regular_user, 'on')
368 self.assertEqual(None, old_link, self.contents)
369 self.assertEqual(None, new_link, self.contents)
370
371 def test_subscribe_link_feature_flag_off_anonymous(self):
372 old_link, new_link = self._create_scenario(
373 ANONYMOUS, None)
374 self.assertNotEqual(None, old_link, self.contents)
375 self.assertEqual(None, new_link, self.contents)
376
377 def test_subscribe_link_feature_flag_on_anonymous(self):
378 old_link, new_link = self._create_scenario(
379 ANONYMOUS, 'on')
380 self.assertEqual(None, old_link, self.contents)
381 self.assertEqual(None, new_link, self.contents)
382
383 def test_subscribe_link_feature_flag_off_bug_super(self):
384 with celebrity_logged_in('admin'):
385 admin = getUtility(ILaunchBag).user
386 self.target.setBugSupervisor(
387 self.regular_user, admin)
388 old_link, new_link = self._create_scenario(
389 self.regular_user, None)
390 self.assertNotEqual(None, old_link, self.contents)
391 self.assertEqual(None, new_link, self.contents)
392
393 def test_subscribe_link_feature_flag_on_bug_super(self):
394 with celebrity_logged_in('admin'):
395 admin = getUtility(ILaunchBag).user
396 self.target.setBugSupervisor(
397 self.regular_user, admin)
398 old_link, new_link = self._create_scenario(
399 self.regular_user, 'on')
400 self.assertEqual(None, old_link, self.contents)
401 self.assertNotEqual(None, new_link, self.contents)
402
403 def test_subscribe_link_feature_flag_off_admin(self):
404 admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
405 old_link, new_link = self._create_scenario(
406 admin, None)
407 self.assertNotEqual(None, old_link, self.contents)
408 self.assertEqual(None, new_link, self.contents)
409
410 def test_subscribe_link_feature_flag_on_admin(self):
411 from lp.testing.sampledata import ADMIN_EMAIL
412 admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
413 old_link, new_link = self._create_scenario(
414 admin, 'on')
415 self.assertEqual(None, old_link, self.contents)
416 self.assertNotEqual(None, new_link, self.contents)
417
418
419class TestDistroMilestoneViewStructSubs(TestDistroViewStructSubs):
420 """Test structural subscriptions on the distro milestones."""
421
422 def setUp(self):
423 super(TestDistroMilestoneViewStructSubs, self).setUp()
424 self.distro = self.target
425 self.target = self.factory.makeMilestone(distribution=self.distro)
426
427 def test_subscribe_link_feature_flag_off_owner(self):
428 old_link, new_link = self._create_scenario(
429 self.distro.owner, None)
430 self.assertNotEqual(None, old_link, self.contents)
431 self.assertEqual(None, new_link, self.contents)
432
433 def test_subscribe_link_feature_flag_on_owner(self):
434 old_link, new_link = self._create_scenario(
435 self.distro.owner, 'on')
436 self.assertEqual(None, old_link, self.contents)
437 self.assertNotEqual(None, new_link, self.contents)
438
439 def test_subscribe_link_feature_flag_off_user(self):
440 old_link, new_link = self._create_scenario(
441 self.regular_user, None)
442 self.assertNotEqual(None, old_link, self.contents)
443 self.assertEqual(None, new_link, self.contents)
444
445 def test_subscribe_link_feature_flag_on_user_no_bug_super(self):
446 old_link, new_link = self._create_scenario(
447 self.regular_user, 'on')
448 self.assertEqual(None, old_link, self.contents)
449 self.assertNotEqual(None, new_link, self.contents)
450
451 def test_subscribe_link_feature_flag_on_user_with_bug_super(self):
452 with celebrity_logged_in('admin'):
453 admin = getUtility(ILaunchBag).user
454 supervisor = self.factory.makePerson()
455 self.distro.setBugSupervisor(
456 supervisor, admin)
457 old_link, new_link = self._create_scenario(
458 self.regular_user, 'on')
459 self.assertEqual(None, old_link, self.contents)
460 self.assertNotEqual(None, new_link, self.contents)
461
462 def test_subscribe_link_feature_flag_off_anonymous(self):
463 old_link, new_link = self._create_scenario(
464 ANONYMOUS, None)
465 self.assertNotEqual(None, old_link, self.contents)
466 self.assertEqual(None, new_link, self.contents)
467
468 def test_subscribe_link_feature_flag_on_anonymous(self):
469 old_link, new_link = self._create_scenario(
470 ANONYMOUS, 'on')
471 self.assertEqual(None, old_link, self.contents)
472 self.assertEqual(None, new_link, self.contents)
473
474 def test_subscribe_link_feature_flag_off_bug_super(self):
475 with celebrity_logged_in('admin'):
476 admin = getUtility(ILaunchBag).user
477 self.distro.setBugSupervisor(
478 self.regular_user, admin)
479 old_link, new_link = self._create_scenario(
480 self.regular_user, None)
481 self.assertNotEqual(None, old_link, self.contents)
482 self.assertEqual(None, new_link, self.contents)
483
484 def test_subscribe_link_feature_flag_on_bug_super(self):
485 with celebrity_logged_in('admin'):
486 admin = getUtility(ILaunchBag).user
487 self.distro.setBugSupervisor(
488 self.regular_user, admin)
489 old_link, new_link = self._create_scenario(
490 self.regular_user, 'on')
491 self.assertEqual(None, old_link, self.contents)
492 self.assertNotEqual(None, new_link, self.contents)
493
494 def test_subscribe_link_feature_flag_off_admin(self):
495 admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
496 old_link, new_link = self._create_scenario(
497 admin, None)
498 self.assertNotEqual(None, old_link, self.contents)
499 self.assertEqual(None, new_link, self.contents)
500
501 def test_subscribe_link_feature_flag_on_admin(self):
502 from lp.testing.sampledata import ADMIN_EMAIL
503 admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
504 old_link, new_link = self._create_scenario(
505 admin, 'on')
506 self.assertEqual(None, old_link, self.contents)
507 self.assertNotEqual(None, new_link, self.contents)
508
509
510class TestProductMilestoneViewStructSubs(TestDistroViewStructSubs):
511 """Test structural subscriptions on the product milestones."""
512
513 def setUp(self):
514 super(TestProductMilestoneViewStructSubs, self).setUp()
515 self.product = self.factory.makeProduct()
516 with person_logged_in(self.product.owner):
517 self.product.official_malone = True
518 self.regular_user = self.factory.makePerson()
519 self.target = self.factory.makeMilestone(product=self.product)
520
521 def test_subscribe_link_feature_flag_off_owner(self):
522 old_link, new_link = self._create_scenario(
523 self.product.owner, None)
524 self.assertNotEqual(None, old_link, self.contents)
525 self.assertEqual(None, new_link, self.contents)
526
527 def test_subscribe_link_feature_flag_on_owner(self):
528 old_link, new_link = self._create_scenario(
529 self.product.owner, 'on')
530 self.assertEqual(None, old_link, self.contents)
531 self.assertNotEqual(None, new_link, self.contents)
532
533 def test_subscribe_link_feature_flag_off_user(self):
534 old_link, new_link = self._create_scenario(
535 self.regular_user, None)
536 self.assertNotEqual(None, old_link, self.contents)
537 self.assertEqual(None, new_link, self.contents)
538
539 # There are no special bug supervisor rules for products.
540 test_subscribe_link_feature_flag_on_user_no_bug_super = None
541 test_subscribe_link_feature_flag_on_user_with_bug_super = None
542 test_subscribe_link_feature_flag_off_bug_super = None
543 test_subscribe_link_feature_flag_on_bug_super = None
544
545 def test_subscribe_link_feature_flag_off_anonymous(self):
546 old_link, new_link = self._create_scenario(
547 ANONYMOUS, None)
548 self.assertNotEqual(None, old_link, self.contents)
549 self.assertEqual(None, new_link, self.contents)
550
551 def test_subscribe_link_feature_flag_on_anonymous(self):
552 old_link, new_link = self._create_scenario(
553 ANONYMOUS, 'on')
554 self.assertEqual(None, old_link, self.contents)
555 self.assertEqual(None, new_link, self.contents)
556
557 def test_subscribe_link_feature_flag_off_admin(self):
558 admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
559 old_link, new_link = self._create_scenario(
560 admin, None)
561 self.assertNotEqual(None, old_link, self.contents)
562 self.assertEqual(None, new_link, self.contents)
563
564 def test_subscribe_link_feature_flag_on_admin(self):
565 from lp.testing.sampledata import ADMIN_EMAIL
566 admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
567 old_link, new_link = self._create_scenario(
568 admin, 'on')
569 self.assertEqual(None, old_link, self.contents)
570 self.assertNotEqual(None, new_link, self.contents)
571
572
573class TestProductSeriesMilestoneViewStructSubs(
574 TestProductMilestoneViewStructSubs):
575 """Test structural subscriptions on the product series milestones."""
576
577 def setUp(self):
578 super(TestProductSeriesMilestoneViewStructSubs, self).setUp()
579 self.productseries = self.factory.makeProductSeries()
580 with person_logged_in(self.productseries.product.owner):
581 self.productseries.product.official_malone = True
582 self.regular_user = self.factory.makePerson()
583 self.target = self.factory.makeMilestone(
584 productseries=self.productseries)
585
586
587def test_suite():
588 """Return the `IStructuralSubscriptionTarget` TestSuite."""
589
590 # Manually construct the test suite to avoid having tests from the base
591 # class _TestStructSubs run.
592 suite = unittest.TestSuite()
593 suite.addTest(unittest.makeSuite(TestProductViewStructSubs))
594 suite.addTest(unittest.makeSuite(TestProductBugsStructSubs))
595 suite.addTest(unittest.makeSuite(TestProductSeriesViewStructSubs))
596 suite.addTest(unittest.makeSuite(TestProductSeriesBugsStructSubs))
597 suite.addTest(unittest.makeSuite(TestProjectGroupViewStructSubs))
598 suite.addTest(unittest.makeSuite(TestProjectGroupBugsStructSubs))
599 suite.addTest(unittest.makeSuite(
600 TestDistributionSourcePackageViewStructSubs))
601 suite.addTest(unittest.makeSuite(
602 TestDistributionSourcePackageBugsStructSubs))
603 suite.addTest(unittest.makeSuite(TestDistroViewStructSubs))
604 suite.addTest(unittest.makeSuite(TestDistroBugsStructSubs))
605 suite.addTest(unittest.makeSuite(TestDistroMilestoneViewStructSubs))
606 suite.addTest(unittest.makeSuite(TestProductMilestoneViewStructSubs))
607 suite.addTest(unittest.makeSuite(
608 TestProductSeriesMilestoneViewStructSubs))
609 return suite
0610
=== modified file 'lib/lp/registry/javascript/structural-subscription.js'
--- lib/lp/registry/javascript/structural-subscription.js 2011-03-29 13:02:44 +0000
+++ lib/lp/registry/javascript/structural-subscription.js 2011-03-29 22:36:23 +0000
@@ -80,7 +80,7 @@
80 tags: [],80 tags: [],
81 find_all_tags: false,81 find_all_tags: false,
82 importances: [],82 importances: [],
83 statuses: [],83 statuses: []
84 };84 };
8585
86 // Set the notification level.86 // Set the notification level.
8787
=== modified file 'lib/lp/registry/javascript/tests/test_structural_subscription.js'
--- lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-03-29 13:02:44 +0000
+++ lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-03-29 22:36:23 +0000
@@ -87,7 +87,7 @@
87 LP.cache.statuses = [];87 LP.cache.statuses = [];
8888
89 this.configuration = {89 this.configuration = {
90 content_box: content_box_id,90 content_box: content_box_id
91 };91 };
92 this.content_node = create_test_node();92 this.content_node = create_test_node();
93 Y.one('body').appendChild(this.content_node);93 Y.one('body').appendChild(this.content_node);
@@ -631,7 +631,7 @@
631 TestBugFilter.prototype = {631 TestBugFilter.prototype = {
632 'getAttrs': function () {632 'getAttrs': function () {
633 return {};633 return {};
634 },634 }
635 };635 };
636636
637 // Now we need an lp_client that will appear to succesfully create637 // Now we need an lp_client that will appear to succesfully create
@@ -648,7 +648,7 @@
648 'patch': function(uri, representation, config, headers) {648 'patch': function(uri, representation, config, headers) {
649 config.on.failure(true, {'status':400});649 config.on.failure(true, {'status':400});
650 patch_failed = true;650 patch_failed = true;
651 },651 }
652 };652 };
653 module.lp_client = new TestClient();653 module.lp_client = new TestClient();
654654
@@ -661,7 +661,7 @@
661 // Delete should have been called and the patch has failed.661 // Delete should have been called and the patch has failed.
662 Assert.isTrue(delete_called);662 Assert.isTrue(delete_called);
663 Assert.isTrue(patch_failed);663 Assert.isTrue(patch_failed);
664 },664 }
665665
666 }));666 }));
667667
@@ -707,7 +707,7 @@
707 var form_data = {707 var form_data = {
708 name: ['filter description'],708 name: ['filter description'],
709 events: [],709 events: [],
710 filters: [],710 filters: []
711 };711 };
712 var patch_data = module._extract_form_data(form_data);712 var patch_data = module._extract_form_data(form_data);
713 Assert.areEqual(patch_data.description, form_data.name[0]);713 Assert.areEqual(patch_data.description, form_data.name[0]);
@@ -719,7 +719,7 @@
719 var form_data = {719 var form_data = {
720 name: [' filter description '],720 name: [' filter description '],
721 events: [],721 events: [],
722 filters: [],722 filters: []
723 };723 };
724 var patch_data = module._extract_form_data(form_data);724 var patch_data = module._extract_form_data(form_data);
725 Assert.areEqual('filter description', patch_data.description);725 Assert.areEqual('filter description', patch_data.description);
@@ -729,7 +729,7 @@
729 var form_data = {729 var form_data = {
730 name: [],730 name: [],
731 events: ['added-or-closed'],731 events: ['added-or-closed'],
732 filters: [],732 filters: []
733 };733 };
734 var patch_data = module._extract_form_data(form_data);734 var patch_data = module._extract_form_data(form_data);
735 Assert.areEqual(735 Assert.areEqual(
@@ -740,7 +740,7 @@
740 var form_data = {740 var form_data = {
741 name: [],741 name: [],
742 events: [],742 events: [],
743 filters: ['filter-comments'],743 filters: ['filter-comments']
744 };744 };
745 var patch_data = module._extract_form_data(form_data);745 var patch_data = module._extract_form_data(form_data);
746 Assert.areEqual(746 Assert.areEqual(
@@ -751,7 +751,7 @@
751 var form_data = {751 var form_data = {
752 name: [],752 name: [],
753 events: [],753 events: [],
754 filters: [],754 filters: []
755 };755 };
756 var patch_data = module._extract_form_data(form_data);756 var patch_data = module._extract_form_data(form_data);
757 Assert.areEqual(757 Assert.areEqual(
@@ -766,7 +766,7 @@
766 tags: ['one two THREE'],766 tags: ['one two THREE'],
767 tag_match: [''],767 tag_match: [''],
768 importances: [],768 importances: [],
769 statuses: [],769 statuses: []
770 };770 };
771 var patch_data = module._extract_form_data(form_data);771 var patch_data = module._extract_form_data(form_data);
772 // Note that the tags are converted to lower case.772 // Note that the tags are converted to lower case.
@@ -782,7 +782,7 @@
782 tags: ['tag'],782 tags: ['tag'],
783 tag_match: ['match-all'],783 tag_match: ['match-all'],
784 importances: [],784 importances: [],
785 statuses: [],785 statuses: []
786 };786 };
787 var patch_data = module._extract_form_data(form_data);787 var patch_data = module._extract_form_data(form_data);
788 Assert.isTrue(patch_data.find_all_tags);788 Assert.isTrue(patch_data.find_all_tags);
@@ -796,7 +796,7 @@
796 tags: ['tag'],796 tags: ['tag'],
797 tag_match: [],797 tag_match: [],
798 importances: [],798 importances: [],
799 statuses: [],799 statuses: []
800 };800 };
801 var patch_data = module._extract_form_data(form_data);801 var patch_data = module._extract_form_data(form_data);
802 Assert.isFalse(patch_data.find_all_tags);802 Assert.isFalse(patch_data.find_all_tags);
@@ -813,16 +813,16 @@
813 tags: ['tag'],813 tags: ['tag'],
814 tag_match: ['match-all'],814 tag_match: ['match-all'],
815 importances: ['importance1'],815 importances: ['importance1'],
816 statuses: ['status1'],816 statuses: ['status1']
817 };817 };
818 var patch_data = module._extract_form_data(form_data);818 var patch_data = module._extract_form_data(form_data);
819 // Since advanced-filter isn't set, all the advanced values should819 // Since advanced-filter isn't set, all the advanced values should
820 // be empty/false despite the form values.820 // be empty/false despite the form values.
821 Assert.isFalse(patch_data.find_all_tags);821 Assert.isFalse(patch_data.find_all_tags);
822 ArrayAssert.isEmpty(patch_data.tags)822 ArrayAssert.isEmpty(patch_data.tags);
823 ArrayAssert.isEmpty(patch_data.importances)823 ArrayAssert.isEmpty(patch_data.importances);
824 ArrayAssert.isEmpty(patch_data.statuses)824 ArrayAssert.isEmpty(patch_data.statuses);
825 },825 }
826826
827 }));827 }));
828828
829829
=== modified file 'lib/lp/registry/templates/distribution-index.pt'
--- lib/lp/registry/templates/distribution-index.pt 2010-10-10 21:54:16 +0000
+++ lib/lp/registry/templates/distribution-index.pt 2011-03-29 22:36:23 +0000
@@ -5,6 +5,22 @@
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_side"6 metal:use-macro="view/macro:page/main_side"
7 i18n:domain="launchpad">7 i18n:domain="launchpad">
8
9 <head>
10 <tal:head-epilogue metal:fill-slot="head_epilogue">
11 <script type="text/javascript"
12 tal:condition="
13 request/features/malone.advanced-structural-subscriptions.enabled">
14 LPS.use('lp.registry.structural_subscription', function(Y) {
15 var module = Y.lp.registry.structural_subscription;
16 Y.on('domready', function() {
17 module.setup({content_box: "#structural-subscription-content-box"});
18 });
19 });
20 </script>
21 </tal:head-epilogue>
22 </head>
23
8 <body>24 <body>
9 <tal:heading metal:fill-slot="heading">25 <tal:heading metal:fill-slot="heading">
10 <h1 tal:content="context/title">project title</h1>26 <h1 tal:content="context/title">project title</h1>
@@ -69,6 +85,9 @@
6985
70 <div tal:replace="structure context/@@+portlet-coming-sprints" />86 <div tal:replace="structure context/@@+portlet-coming-sprints" />
71 </div>87 </div>
88 <div class="yui-u">
89 <div id="structural-subscription-content-box"></div>
90 </div>
72 </div>91 </div>
73 </tal:main>92 </tal:main>
7493
7594
=== modified file 'lib/lp/registry/templates/distributionsourcepackage-index.pt'
--- lib/lp/registry/templates/distributionsourcepackage-index.pt 2011-03-23 16:28:51 +0000
+++ lib/lp/registry/templates/distributionsourcepackage-index.pt 2011-03-29 22:36:23 +0000
@@ -16,6 +16,16 @@
16 tal:attributes="src string:${lp_js}/soyuz/base.js"></script>16 tal:attributes="src string:${lp_js}/soyuz/base.js"></script>
17 </tal:archive_js>17 </tal:archive_js>
18 </tal:devmode>18 </tal:devmode>
19 <script type="text/javascript"
20 tal:condition="
21 request/features/malone.advanced-structural-subscriptions.enabled">
22 LPS.use('lp.registry.structural_subscription', function(Y) {
23 var module = Y.lp.registry.structural_subscription;
24 Y.on('domready', function() {
25 module.setup({content_box: "#structural-subscription-content-box"});
26 });
27 });
28 </script>
19</metal:block>29</metal:block>
2030
21<tal:side metal:fill-slot="side">31<tal:side metal:fill-slot="side">
@@ -28,6 +38,9 @@
28</tal:side>38</tal:side>
2939
30<tal:main metal:fill-slot="main">40<tal:main metal:fill-slot="main">
41 <div class="yui-u">
42 <div id="structural-subscription-content-box"></div>
43 </div>
31 <div class="top-portlet" id="bugs-and-questions-summary"44 <div class="top-portlet" id="bugs-and-questions-summary"
32 tal:define="newbugs context/new_bugtasks/count;45 tal:define="newbugs context/new_bugtasks/count;
33 open_questions view/open_questions/count">46 open_questions view/open_questions/count">
@@ -169,7 +182,7 @@
169 <tr tal:attributes="id string:pub${pubid}" style="display: none">182 <tr tal:attributes="id string:pub${pubid}" style="display: none">
170 <td colspan="3">183 <td colspan="3">
171 <div class="package-details"184 <div class="package-details"
172 tal:attributes="id string:pub${pubid}-container" />185 tal:attributes="id string:pub${pubid}-container"></div>
173 </td>186 </td>
174 </tr>187 </tr>
175188
176189
=== modified file 'lib/lp/registry/templates/distroseries-index.pt'
--- lib/lp/registry/templates/distroseries-index.pt 2010-10-10 21:54:16 +0000
+++ lib/lp/registry/templates/distroseries-index.pt 2011-03-29 22:36:23 +0000
@@ -5,6 +5,7 @@
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_side"6 metal:use-macro="view/macro:page/main_side"
7 i18n:domain="launchpad">7 i18n:domain="launchpad">
8
8 <body>9 <body>
9 <metal:block fill-slot="head_epilogue">10 <metal:block fill-slot="head_epilogue">
10 <metal:yui-dependencies11 <metal:yui-dependencies
@@ -12,6 +13,16 @@
12 <script id="milestone-script" type="text/javascript"13 <script id="milestone-script" type="text/javascript"
13 tal:condition="context/menu:overview/create_milestone/enabled"14 tal:condition="context/menu:overview/create_milestone/enabled"
14 tal:content="view/register_milestone_script"></script>15 tal:content="view/register_milestone_script"></script>
16 <script type="text/javascript"
17 tal:condition="
18 request/features/malone.advanced-structural-subscriptions.enabled">
19 LPS.use('lp.registry.structural_subscription', function(Y) {
20 var module = Y.lp.registry.structural_subscription;
21 Y.on('domready', function() {
22 module.setup({content_box: "#structural-subscription-content-box"});
23 });
24 });
25 </script>
15 </metal:block>26 </metal:block>
1627
17 <tal:heading metal:fill-slot="heading">28 <tal:heading metal:fill-slot="heading">
@@ -108,12 +119,16 @@
108 </ul>119 </ul>
109 </div>120 </div>
110 </div>121 </div>
122 <div class="yui-u">
123 <div id="structural-subscription-content-box"></div>
124 </div>
111 </div>125 </div>
112 </div>126 </div>
113127
114 <tal:side metal:fill-slot="side"128 <tal:side metal:fill-slot="side"
115 define="overview_menu context/menu:overview">129 define="overview_menu context/menu:overview">
116 <div id="global-actions" class="portlet">130 <div id="global-actions" class="portlet"
131 condition="overview_menu/subscribe_to_bug_mail/enabled|overview_menu/subscribe/enabled|nothing">
117 <ul>132 <ul>
118 <li tal:condition="overview_menu/edit/enabled">133 <li tal:condition="overview_menu/edit/enabled">
119 <a tal:replace="structure overview_menu/edit/fmt:link" />134 <a tal:replace="structure overview_menu/edit/fmt:link" />
@@ -124,9 +139,12 @@
124 <li tal:condition="overview_menu/reassign/enabled">139 <li tal:condition="overview_menu/reassign/enabled">
125 <a tal:replace="structure overview_menu/reassign/fmt:link" />140 <a tal:replace="structure overview_menu/reassign/fmt:link" />
126 </li>141 </li>
127 <li>142 <li tal:condition="overview_menu/subscribe/enabled|nothing">
128 <a tal:replace="structure overview_menu/subscribe/fmt:link" />143 <a tal:replace="structure overview_menu/subscribe/fmt:link" />
129 </li>144 </li>
145 <li tal:condition="overview_menu/subscribe_to_bug_mail/enabled|nothing">
146 <a tal:replace="structure overview_menu/subscribe_to_bug_mail/fmt:link" />
147 </li>
130 </ul>148 </ul>
131 </div>149 </div>
132150
133151
=== modified file 'lib/lp/registry/templates/milestone-index.pt'
--- lib/lp/registry/templates/milestone-index.pt 2011-03-04 00:08:20 +0000
+++ lib/lp/registry/templates/milestone-index.pt 2011-03-29 22:36:23 +0000
@@ -6,14 +6,24 @@
6 metal:use-macro="view/macro:page/main_side"6 metal:use-macro="view/macro:page/main_side"
7 i18n:domain="launchpad">7 i18n:domain="launchpad">
88
9 <tal:css metal:fill-slot="head_epilogue"9 <tal:head-epilogue metal:fill-slot="head_epilogue"
10 condition="view/is_project_milestone">10 condition="view/is_project_milestone">
11 <style id="hide-side-portlets" type="text/css">11 <style id="hide-side-portlets" type="text/css">
12 .side {12 .side {
13 background: #fff;13 background: #fff;
14 }14 }
15 </style>15 </style>
16 </tal:css>16 <script type="text/javascript"
17 tal:condition="
18 request/features/malone.advanced-structural-subscriptions.enabled">
19 LPS.use('lp.registry.structural_subscription', function(Y) {
20 var module = Y.lp.registry.structural_subscription;
21 Y.on('domready', function() {
22 module.setup({content_box: "#structural-subscription-content-box"});
23 });
24 });
25 </script>
26 </tal:head-epilogue>
1727
18 <body>28 <body>
19 <tal:heading metal:fill-slot="heading">29 <tal:heading metal:fill-slot="heading">
@@ -308,6 +318,9 @@
308 </li>318 </li>
309 </ul>319 </ul>
310 </div>320 </div>
321 <div class="yui-u">
322 <div id="structural-subscription-content-box"></div>
323 </div>
311 </div>324 </div>
312325
313 <tal:side metal:fill-slot="side">326 <tal:side metal:fill-slot="side">
314327
=== modified file 'lib/lp/registry/templates/product-index.pt'
--- lib/lp/registry/templates/product-index.pt 2011-03-23 15:55:44 +0000
+++ lib/lp/registry/templates/product-index.pt 2011-03-29 22:36:23 +0000
@@ -34,9 +34,9 @@
3434
35 <script type="text/javascript"35 <script type="text/javascript"
36 tal:condition="36 tal:condition="
37 request/features/advanced-structural-subscriptions.enabled">37 request/features/malone.advanced-structural-subscriptions.enabled">
38 LPS.use('lp.registry.structural_subscription', function(Y) {38 LPS.use('lp.registry.structural_subscription', function(Y) {
39 module = Y.lp.registry.structural_subscription;39 var module = Y.lp.registry.structural_subscription;
40 Y.on('domready', function() {40 Y.on('domready', function() {
41 module.setup({content_box: "#structural-subscription-content-box"});41 module.setup({content_box: "#structural-subscription-content-box"});
42 });42 });
4343
=== modified file 'lib/lp/registry/templates/productseries-index.pt'
--- lib/lp/registry/templates/productseries-index.pt 2011-03-23 16:28:51 +0000
+++ lib/lp/registry/templates/productseries-index.pt 2011-03-29 22:36:23 +0000
@@ -14,6 +14,16 @@
14 <script id="milestone-script" type="text/javascript"14 <script id="milestone-script" type="text/javascript"
15 tal:condition="context/menu:overview/create_milestone/enabled"15 tal:condition="context/menu:overview/create_milestone/enabled"
16 tal:content="view/register_milestone_script"></script>16 tal:content="view/register_milestone_script"></script>
17 <script type="text/javascript"
18 tal:condition="
19 request/features/malone.advanced-structural-subscriptions.enabled">
20 LPS.use('lp.registry.structural_subscription', function(Y) {
21 var module = Y.lp.registry.structural_subscription;
22 Y.on('domready', function() {
23 module.setup({content_box: "#structural-subscription-content-box"});
24 });
25 });
26 </script>
17 </metal:block>27 </metal:block>
1828
19 <tal:heading metal:fill-slot="heading">29 <tal:heading metal:fill-slot="heading">
@@ -178,12 +188,17 @@
178 </ul>188 </ul>
179 </div>189 </div>
180 </div>190 </div>
191 <div class="yui-u">
192 <div id="structural-subscription-content-box"></div>
193 </div>
181 </div>194 </div>
182 </div>195 </div>
183196
184 <tal:side metal:fill-slot="side"197 <tal:side metal:fill-slot="side"
185 define="overview_menu context/menu:overview">198 define="overview_menu context/menu:overview;
186 <div id="global-actions" class="portlet">199 feature_flag request/features/malone.advanced-structural-subscriptions.enabled">
200 <div id="global-actions" class="portlet"
201 tal:condition="overview_menu/subscribe_to_bug_mail/enabled|overview_menu/subscribe/enabled">
187 <ul>202 <ul>
188 <li tal:condition="overview_menu/edit/enabled">203 <li tal:condition="overview_menu/edit/enabled">
189 <a tal:replace="structure overview_menu/edit/fmt:link" />204 <a tal:replace="structure overview_menu/edit/fmt:link" />
@@ -191,13 +206,21 @@
191 <li tal:condition="overview_menu/delete/enabled">206 <li tal:condition="overview_menu/delete/enabled">
192 <a tal:replace="structure overview_menu/delete/fmt:link" />207 <a tal:replace="structure overview_menu/delete/fmt:link" />
193 </li>208 </li>
194 <li>209 <tal:advanced-structural-subscriptions
195 <a tal:replace="structure overview_menu/subscribe/fmt:link" />210 condition="feature_flag">
196 </li>211 <li tal:condition="overview_menu/subscribe_to_bug_mail/enabled|nothing">
212 <a tal:replace="structure overview_menu/subscribe_to_bug_mail/fmt:link" />
213 </li>
214 </tal:advanced-structural-subscriptions>
215 <tal:not-advanced-structural-subscriptions
216 condition="not: feature_flag">
217 <li tal:condition="overview_menu/subscribe/enabled|nothing">
218 <a tal:replace="structure overview_menu/subscribe/fmt:link" />
219 </li>
220 </tal:not-advanced-structural-subscriptions>
197 </ul>221 </ul>
198 </div>222 </div>
199223
200
201 <div id="downloads" class="top-portlet downloads"224 <div id="downloads" class="top-portlet downloads"
202 tal:define="release view/latest_release_with_download_files">225 tal:define="release view/latest_release_with_download_files">
203 <h2>Downloads</h2>226 <h2>Downloads</h2>
204227
=== modified file 'lib/lp/registry/templates/project-index.pt'
--- lib/lp/registry/templates/project-index.pt 2010-10-10 21:54:16 +0000
+++ lib/lp/registry/templates/project-index.pt 2011-03-29 22:36:23 +0000
@@ -6,6 +6,22 @@
6 metal:use-macro="view/macro:page/main_side"6 metal:use-macro="view/macro:page/main_side"
7 i18n:domain="launchpad"7 i18n:domain="launchpad"
8>8>
9
10 <head>
11 <tal:head-epilogue metal:fill-slot="head_epilogue">
12 <script type="text/javascript"
13 tal:condition="
14 request/features/malone.advanced-structural-subscriptions.enabled">
15 LPS.use('lp.registry.structural_subscription', function(Y) {
16 var module = Y.lp.registry.structural_subscription;
17 Y.on('domready', function() {
18 module.setup({content_box: "#structural-subscription-content-box"});
19 });
20 });
21 </script>
22 </tal:head-epilogue>
23 </head>
24
9 <body>25 <body>
10 <tal:registering metal:fill-slot="registering">26 <tal:registering metal:fill-slot="registering">
11 Registered27 Registered
@@ -127,6 +143,9 @@
127 <tal:sprints content="structure context/@@+portlet-coming-sprints" />143 <tal:sprints content="structure context/@@+portlet-coming-sprints" />
128 </tal:has-few-project>144 </tal:has-few-project>
129 </div>145 </div>
146 <div class="yui-u">
147 <div id="structural-subscription-content-box"></div>
148 </div>
130 </div>149 </div>
131 </tal:main>150 </tal:main>
132151
133152
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2011-03-28 05:33:33 +0000
+++ lib/lp/services/features/flags.py 2011-03-29 22:36:23 +0000
@@ -53,6 +53,10 @@
53 'boolean',53 'boolean',
54 'Enables advanced bug subscription features.',54 'Enables advanced bug subscription features.',
55 ''),55 ''),
56 ('malone.advanced-structural-subscriptions.enabled',
57 'boolean',
58 'Enables advanced structural subscriptions',
59 ''),
56 ('malone.disable_targetnamesearch',60 ('malone.disable_targetnamesearch',
57 'boolean',61 'boolean',
58 'If true, disables consultation of target names during bug text search.',62 'If true, disables consultation of target names during bug text search.',