Merge lp:~brian-murray/launchpad/bug-546078 into lp:launchpad

Proposed by Brian Murray
Status: Merged
Approved by: Deryck Hodge
Approved revision: no longer in the source branch.
Merged at revision: 10959
Proposed branch: lp:~brian-murray/launchpad/bug-546078
Merge into: lp:launchpad
Diff against target: 687 lines (+357/-35)
11 files modified
lib/lp/bugs/browser/tests/test_bugtarget_patches_view.py (+3/-3)
lib/lp/bugs/doc/bugtask-search.txt (+170/-3)
lib/lp/bugs/interfaces/bug.py (+1/-1)
lib/lp/bugs/interfaces/bugtarget.py (+3/-1)
lib/lp/bugs/interfaces/bugtask.py (+6/-3)
lib/lp/bugs/model/bugtarget.py (+1/-0)
lib/lp/bugs/model/bugtask.py (+45/-2)
lib/lp/bugs/stories/patches-view/patches-view.txt (+101/-7)
lib/lp/bugs/stories/structural-subscriptions/xx-bug-subscriptions.txt (+1/-1)
lib/lp/bugs/stories/webservice/xx-bug.txt (+6/-5)
lib/lp/registry/tests/test_person.py (+20/-9)
To merge this branch: bzr merge lp:~brian-murray/launchpad/bug-546078
Reviewer Review Type Date Requested Status
Eleanor Berger (community) Approve
Review via email: mp+25740@code.launchpad.net

Description of the change

This branch modifies the +patches view so that bug tasks that a person, or team, are structurally subscribed to appear in the +patches view in addition to those that already appear. This fixes bug 546078.

Additionally, some drive by fixes, for issues I found when reading code, were made:
adding the new expired status to the test_bugtarget_patches_view.py test
wording change of expired definition in lib/lp/bugs/interfaces/bugtask.py
wording change of doc string for get_related_bugtasks_search_params in lib/lp/bugs/model/bugtask.py
typo fix for distribution drivers in lib/lp/bugs/stories/structural-subscriptions/xx-bug-subscriptions.txt

Tests modified:
lib/lp/bugs/doc/bugtask-search.txt
lib/lp/bugs/stories/patches-view/patches-view.txt

To post a comment you must log in.
Revision history for this message
Eleanor Berger (intellectronica) wrote :

Very nice, everything looks pretty good, but there's, I think, one problem. If I understand the code correctly, this works only for subscriptions to products, not to other subscription targets (like packages, project groups, etc). When or before fixing this, it's worth extending the test to cover those other subscription targets too.

review: Needs Information
Revision history for this message
Eleanor Berger (intellectronica) wrote :

As we discussed on IRC, this branch now looks very good. We had one concern, over duplicate results because of the UNION of a subscription to a target and its parent target (like Product and ProjectGroup), so you're going to verify that this isn't the case and fix that if necessary. After that you can land.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_patches_view.py'
--- lib/lp/bugs/browser/tests/test_bugtarget_patches_view.py 2010-03-11 22:14:51 +0000
+++ lib/lp/bugs/browser/tests/test_bugtarget_patches_view.py 2010-06-07 18:13:25 +0000
@@ -15,7 +15,6 @@
15from lp.bugs.interfaces.bugtask import BugTaskStatus15from lp.bugs.interfaces.bugtask import BugTaskStatus
16from lp.testing import TestCaseWithFactory16from lp.testing import TestCaseWithFactory
1717
18
19DISPLAY_BUG_STATUS_FOR_PATCHES = {18DISPLAY_BUG_STATUS_FOR_PATCHES = {
20 BugTaskStatus.NEW: True,19 BugTaskStatus.NEW: True,
21 BugTaskStatus.INCOMPLETE: True,20 BugTaskStatus.INCOMPLETE: True,
@@ -27,6 +26,7 @@
27 BugTaskStatus.FIXCOMMITTED: True,26 BugTaskStatus.FIXCOMMITTED: True,
28 BugTaskStatus.FIXRELEASED: False,27 BugTaskStatus.FIXRELEASED: False,
29 BugTaskStatus.UNKNOWN: False,28 BugTaskStatus.UNKNOWN: False,
29 BugTaskStatus.EXPIRED: False
30 }30 }
3131
3232
@@ -54,8 +54,8 @@
5454
55 def test_status_of_bugs_with_patches_shown(self):55 def test_status_of_bugs_with_patches_shown(self):
56 # Bugs with patches that have the status FIXRELEASED, INVALID,56 # Bugs with patches that have the status FIXRELEASED, INVALID,
57 # WONTFIX, UNKNOWN are not shown in the +patches view; all other57 # WONTFIX, UNKNOWN, EXPIRED are not shown in the +patches view; all
58 # bugs are shown.58 # other bugs are shown.
59 number_of_bugs_shown = 059 number_of_bugs_shown = 0
60 for bugtask_status in DISPLAY_BUG_STATUS_FOR_PATCHES:60 for bugtask_status in DISPLAY_BUG_STATUS_FOR_PATCHES:
61 if DISPLAY_BUG_STATUS_FOR_PATCHES[bugtask_status]:61 if DISPLAY_BUG_STATUS_FOR_PATCHES[bugtask_status]:
6262
=== modified file 'lib/lp/bugs/doc/bugtask-search.txt'
--- lib/lp/bugs/doc/bugtask-search.txt 2010-04-12 07:53:48 +0000
+++ lib/lp/bugs/doc/bugtask-search.txt 2010-06-07 18:13:25 +0000
@@ -92,8 +92,8 @@
9292
93=== Product bug supervisor ===93=== Product bug supervisor ===
9494
95If No Privileges is specified as Firefox' bug supervisor, searching for his95If No Privileges is specified as Firefox's bug supervisor, searching for his
96bugs return all of Firefox' bugs.96bugs return all of Firefox's bugs.
9797
98 >>> login('foo.bar@canonical.com')98 >>> login('foo.bar@canonical.com')
99 >>> from canonical.launchpad.ftests import syncUpdate99 >>> from canonical.launchpad.ftests import syncUpdate
@@ -989,7 +989,7 @@
989 4 2989 4 2
990 5 1990 5 1
991991
992Similary, we can search for bugs that do not have any linked branches.992Similarly, we can search for bugs that do not have any linked branches.
993993
994 >>> from lp.bugs.interfaces.bugtask import BugBranchSearch994 >>> from lp.bugs.interfaces.bugtask import BugBranchSearch
995 >>> search_params = BugTaskSearchParams(995 >>> search_params = BugTaskSearchParams(
@@ -1197,3 +1197,170 @@
1197 ... importance=[BugTaskImportance.LOW, BugTaskImportance.MEDIUM]))1197 ... importance=[BugTaskImportance.LOW, BugTaskImportance.MEDIUM]))
1198 4 Mozilla Firefox Reflow problems with complex page layouts NEW MEDIUM1198 4 Mozilla Firefox Reflow problems with complex page layouts NEW MEDIUM
1199 1 Mozilla Firefox Firefox does not support SVG NEW LOW1199 1 Mozilla Firefox Firefox does not support SVG NEW LOW
1200
1201
1202== Searching by structural subscriber ==
1203
1204The 'structural_subscriber' search parameter allows one to search all the bug
1205tasks to which a person is structurally subscribed. A person can be a
1206structural subscriber for a product, a product series, a project, a milestone,
1207a distribution, a distribution series and a distribution source package. No
1208Privileges Person isn't a structural subscriber, so no bug tasks are found:
1209
1210 >>> from canonical.launchpad.interfaces import IPersonSet
1211 >>> no_priv = getUtility(IPersonSet).getByName('no-priv')
1212 >>> no_priv_struct_sub = BugTaskSearchParams(
1213 ... user=None, structural_subscriber=no_priv)
1214 >>> found_bugtasks = bugtask_set.search(no_priv_struct_sub)
1215 >>> found_bugtasks.count()
1216 0
1217
1218Create a new person and make them a subscriber to all Firefox (product) bug
1219reports. Subsequently, we confirm that they are subscribed to all of the
1220Firefox bug tasks.
1221
1222 >>> product_struct_subber = factory.makePerson(
1223 ... name='product-struct-subber')
1224 >>> firefox.addBugSubscription(product_struct_subber,
1225 ... product_struct_subber)
1226 <StructuralSubscription at ...>
1227 >>> product_struct_sub_search = BugTaskSearchParams(
1228 ... user=None, structural_subscriber=product_struct_subber)
1229 >>> found_bugtasks = bugtask_set.search(product_struct_sub_search)
1230 >>> found_bugtasks.count()
1231 7
1232
1233Create a new person and subscribe them to all of the bug tasks for a product
1234series. We then test to see that they are subscribed to all of the bug tasks
1235for the product series in which they are interested.
1236
1237 >>> product_series = firefox.getSeries('1.0')
1238 >>> all_targeted = BugTaskSearchParams(user=None, omit_targeted=False)
1239 >>> series_tasks = product_series.searchTasks(all_targeted)
1240 >>> series_struct_subber = factory.makePerson(
1241 ... name='series-struct-subber')
1242 >>> product_series.addBugSubscription(series_struct_subber,
1243 ... series_struct_subber)
1244 <StructuralSubscription at ...>
1245 >>> series_struct_sub_search = BugTaskSearchParams(
1246 ... user=None, structural_subscriber=series_struct_subber)
1247 >>> found_bugtasks = bugtask_set.search(series_struct_sub_search)
1248 >>> found_bugtasks.count()
1249 2
1250
1251Create a new product which will be a part of a project group. A bug is
1252created for the product which should then show up for structural subscribers
1253of the project group. Then a new person is created who is subscribed to all
1254of the bug reports about the project. Search for bug tasks that this new
1255person is subscribed.
1256
1257 >>> product = factory.makeProduct()
1258 >>> bug = factory.makeBug(product=product)
1259 >>> project = factory.makeProject()
1260 >>> product.project = project
1261 >>> project_struct_subber = factory.makePerson(
1262 ... name='project-struct-subber')
1263 >>> project.addBugSubscription(project_struct_subber,
1264 ... project_struct_subber)
1265 <StructuralSubscription at ...>
1266 >>> project_struct_sub_search = BugTaskSearchParams(
1267 ... user=None, structural_subscriber=project_struct_subber)
1268 >>> found_bugtasks = bugtask_set.search(project_struct_sub_search)
1269 >>> found_bugtasks.count()
1270 1
1271
1272We will also subscribe this project subscriber to a product that is a part of
1273the project and ensure that duplicate bug tasks do not appear in the search
1274results.
1275
1276 >>> product2 = factory.makeProduct()
1277 >>> bug = factory.makeBug(product=product2)
1278 >>> product2.project = project
1279 >>> product2.addBugSubscription(project_struct_subber,
1280 ... project_struct_subber)
1281 <StructuralSubscription at ...>
1282 >>> project_struct_sub_search = BugTaskSearchParams(
1283 ... user=None, structural_subscriber=project_struct_subber)
1284 >>> found_bugtasks = bugtask_set.search(project_struct_sub_search)
1285 >>> found_bugtasks.count()
1286 2
1287
1288Create a new person and subscribe them to all of bug tasks targeted to a
1289milestone. We then test to see that they are subscribed to all of the
1290bug tasks for the milestone in which they are interested.
1291
1292 >>> milestone_struct_subber = factory.makePerson(
1293 ... name='milestone-struct-subber')
1294 >>> product_milestone.addBugSubscription(milestone_struct_subber,
1295 ... milestone_struct_subber)
1296 <StructuralSubscription at ...>
1297 >>> milestone_struct_sub_search = BugTaskSearchParams(
1298 ... user=None, structural_subscriber=milestone_struct_subber)
1299 >>> found_bugtasks = bugtask_set.search(milestone_struct_sub_search)
1300 >>> found_bugtasks.count()
1301 2
1302
1303Create another new person and subscribe them to all the Ubuntu bug reports -
1304crazy I know. Then test to see that this poor person is subscribed to all
1305bugs with an Ubuntu bug task.
1306
1307 >>> distro_struct_subber = factory.makePerson(
1308 ... name='distro-struct-subber')
1309 >>> ubuntu.addBugSubscription(distro_struct_subber,
1310 ... distro_struct_subber)
1311 <StructuralSubscription at ...>
1312 >>> distro_struct_sub_search = BugTaskSearchParams(
1313 ... user=None, structural_subscriber=distro_struct_subber)
1314 >>> found_bugtasks = bugtask_set.search(distro_struct_sub_search)
1315 >>> found_bugtasks.count()
1316 5
1317
1318Create a new person who will only be subscribed to an Ubuntu series, which is
1319something more reasonable than all of Ubuntu. Test to ensure that this person
1320is subscribed to all of the bug tasks about that distro series.
1321
1322 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
1323 >>> hoary = ubuntu.getSeries('hoary')
1324 >>> distro_series_struct_subber = factory.makePerson(
1325 ... name='distro-series-struct-subber')
1326 >>> hoary.addBugSubscription(distro_series_struct_subber,
1327 ... distro_series_struct_subber)
1328 <StructuralSubscription at ...>
1329 >>> distro_series_struct_sub_search = BugTaskSearchParams(
1330 ... user=None, structural_subscriber=distro_series_struct_subber)
1331 >>> found_bugtasks = bugtask_set.search(distro_series_struct_sub_search)
1332 >>> all_targeted = BugTaskSearchParams(user=None, omit_targeted=False)
1333 >>> hoary_bugtasks = hoary.searchTasks(all_targeted)
1334 >>> found_bugtasks.count() == hoary_bugtasks.count()
1335 True
1336
1337Create a new person and make them a subscriber to all Ubuntu Firefox (a
1338distribution source package) bug reports. Test to see that the new person is
1339subscribed to all of the Ubuntu Firefox bug tasks.
1340
1341 >>> package_struct_subber = factory.makePerson(
1342 ... name='package-struct-subber')
1343 >>> ubuntu_firefox.addBugSubscription(package_struct_subber,
1344 ... package_struct_subber)
1345 <StructuralSubscription at ...>
1346 >>> package_struct_sub_search = BugTaskSearchParams(
1347 ... user=None, structural_subscriber=package_struct_subber)
1348 >>> found_bugtasks = bugtask_set.search(package_struct_sub_search)
1349 >>> found_bugtasks.count()
1350 1
1351
1352We'll also subscribe the person who is currently subscribed to a package's bug
1353reports, package_struct_subber, to the bug reports of a product series to
1354ensure that the structural_subscriber search is returning the set of both bug
1355tasks.
1356
1357 >>> product_series.addBugSubscription(package_struct_subber,
1358 ... package_struct_subber)
1359 <StructuralSubscription at ...>
1360 >>> package_struct_sub_search = BugTaskSearchParams(
1361 ... user=None, structural_subscriber=package_struct_subber)
1362 >>> found_bugtasks = bugtask_set.search(package_struct_sub_search)
1363 >>> combined_bugtasks_count = (ubuntu_firefox_bugs.count () +
1364 ... series_tasks.count())
1365 >>> found_bugtasks.count() == combined_bugtasks_count
1366 True
12001367
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2010-06-04 02:29:33 +0000
+++ lib/lp/bugs/interfaces/bug.py 2010-06-07 18:13:25 +0000
@@ -97,7 +97,7 @@
97 False in a boolean context, or an AssertionError will be raised.97 False in a boolean context, or an AssertionError will be raised.
9898
99 If distribution is specified, sourcepackagename may optionally99 If distribution is specified, sourcepackagename may optionally
100 be provided. product must evaluate to False in a boolean100 be provided. Product must evaluate to False in a boolean
101 context, or an AssertionError will be raised.101 context, or an AssertionError will be raised.
102 """102 """
103 assert product or distribution, (103 assert product or distribution, (
104104
=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py 2010-05-06 01:46:55 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py 2010-06-07 18:13:25 +0000
@@ -76,6 +76,7 @@
76 bug_supervisor=Reference(schema=Interface),76 bug_supervisor=Reference(schema=Interface),
77 bug_commenter=Reference(schema=Interface),77 bug_commenter=Reference(schema=Interface),
78 bug_subscriber=Reference(schema=Interface),78 bug_subscriber=Reference(schema=Interface),
79 structural_subscriber=Reference(schema=Interface),
79 owner=Reference(schema=Interface),80 owner=Reference(schema=Interface),
80 affected_user=Reference(schema=Interface),81 affected_user=Reference(schema=Interface),
81 has_patch=copy_field(IBugTaskSearch['has_patch']),82 has_patch=copy_field(IBugTaskSearch['has_patch']),
@@ -184,7 +185,8 @@
184 hardware_owner_is_bug_reporter=None,185 hardware_owner_is_bug_reporter=None,
185 hardware_owner_is_affected_by_bug=False,186 hardware_owner_is_affected_by_bug=False,
186 hardware_owner_is_subscribed_to_bug=False,187 hardware_owner_is_subscribed_to_bug=False,
187 hardware_is_linked_to_bug=False, linked_branches=None):188 hardware_is_linked_to_bug=False, linked_branches=None,
189 structural_subscriber=None):
188 """Search the IBugTasks reported on this entity.190 """Search the IBugTasks reported on this entity.
189191
190 :search_params: a BugTaskSearchParams object192 :search_params: a BugTaskSearchParams object
191193
=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py 2010-05-25 16:45:26 +0000
+++ lib/lp/bugs/interfaces/bugtask.py 2010-06-07 18:13:25 +0000
@@ -175,7 +175,7 @@
175 EXPIRED = DBItem(19, """175 EXPIRED = DBItem(19, """
176 Expired176 Expired
177177
178 This bug is expired. There was no activity since a longer time.178 This bug is expired. There was no activity for a long time.
179 """)179 """)
180180
181 CONFIRMED = DBItem(20, """181 CONFIRMED = DBItem(20, """
@@ -1075,7 +1075,7 @@
1075 hardware_owner_is_affected_by_bug=False,1075 hardware_owner_is_affected_by_bug=False,
1076 hardware_owner_is_subscribed_to_bug=False,1076 hardware_owner_is_subscribed_to_bug=False,
1077 hardware_is_linked_to_bug=False,1077 hardware_is_linked_to_bug=False,
1078 linked_branches=None1078 linked_branches=None, structural_subscriber=None
1079 ):1079 ):
10801080
1081 self.bug = bug1081 self.bug = bug
@@ -1120,6 +1120,7 @@
1120 hardware_owner_is_subscribed_to_bug)1120 hardware_owner_is_subscribed_to_bug)
1121 self.hardware_is_linked_to_bug = hardware_is_linked_to_bug1121 self.hardware_is_linked_to_bug = hardware_is_linked_to_bug
1122 self.linked_branches = linked_branches1122 self.linked_branches = linked_branches
1123 self.structural_subscriber = structural_subscriber
11231124
1124 def setProduct(self, product):1125 def setProduct(self, product):
1125 """Set the upstream context on which to filter the search."""1126 """Set the upstream context on which to filter the search."""
@@ -1192,7 +1193,8 @@
1192 hardware_owner_is_bug_reporter=None,1193 hardware_owner_is_bug_reporter=None,
1193 hardware_owner_is_affected_by_bug=False,1194 hardware_owner_is_affected_by_bug=False,
1194 hardware_owner_is_subscribed_to_bug=False,1195 hardware_owner_is_subscribed_to_bug=False,
1195 hardware_is_linked_to_bug=False, linked_branches=None):1196 hardware_is_linked_to_bug=False, linked_branches=None,
1197 structural_subscriber=None):
1196 """Create and return a new instance using the parameter list."""1198 """Create and return a new instance using the parameter list."""
1197 search_params = cls(user=user, orderby=order_by)1199 search_params = cls(user=user, orderby=order_by)
11981200
@@ -1260,6 +1262,7 @@
1260 search_params.hardware_is_linked_to_bug = (1262 search_params.hardware_is_linked_to_bug = (
1261 hardware_is_linked_to_bug)1263 hardware_is_linked_to_bug)
1262 search_params.linked_branches=linked_branches1264 search_params.linked_branches=linked_branches
1265 search_params.structural_subscriber = structural_subscriber
12631266
1264 return search_params1267 return search_params
12651268
12661269
=== modified file 'lib/lp/bugs/model/bugtarget.py'
--- lib/lp/bugs/model/bugtarget.py 2010-04-19 08:31:01 +0000
+++ lib/lp/bugs/model/bugtarget.py 2010-06-07 18:13:25 +0000
@@ -51,6 +51,7 @@
51 importance=None,51 importance=None,
52 assignee=None, bug_reporter=None, bug_supervisor=None,52 assignee=None, bug_reporter=None, bug_supervisor=None,
53 bug_commenter=None, bug_subscriber=None, owner=None,53 bug_commenter=None, bug_subscriber=None, owner=None,
54 structural_subscriber=None,
54 affected_user=None, affects_me=False,55 affected_user=None, affects_me=False,
55 has_patch=None, has_cve=None, distribution=None,56 has_patch=None, has_cve=None, distribution=None,
56 tags=None, tags_combinator=BugTagsSearchCombinator.ALL,57 tags=None, tags_combinator=BugTagsSearchCombinator.ALL,
5758
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2010-05-27 13:51:06 +0000
+++ lib/lp/bugs/model/bugtask.py 2010-06-07 18:13:25 +0000
@@ -149,7 +149,7 @@
149 search for all tasks related to a user given by `context`.149 search for all tasks related to a user given by `context`.
150150
151 Which tasks are related to a user?151 Which tasks are related to a user?
152 * the user has to be either assignee or owner of this tasks152 * the user has to be either assignee or owner of this task
153 OR153 OR
154 * the user has to be subscriber or commenter to the underlying bug154 * the user has to be subscriber or commenter to the underlying bug
155 OR155 OR
@@ -158,7 +158,8 @@
158 always get one task owned by the bug reporter158 always get one task owned by the bug reporter
159 """159 """
160 assert IPerson.providedBy(context), "Context argument needs to be IPerson"160 assert IPerson.providedBy(context), "Context argument needs to be IPerson"
161 relevant_fields = ('assignee', 'bug_subscriber', 'owner', 'bug_commenter')161 relevant_fields = ('assignee', 'bug_subscriber', 'owner', 'bug_commenter',
162 'structural_subscriber')
162 search_params = []163 search_params = []
163 for key in relevant_fields:164 for key in relevant_fields:
164 # all these parameter default to None165 # all these parameter default to None
@@ -1641,6 +1642,48 @@
1641 BugSubscription.person = %(personid)s""" %1642 BugSubscription.person = %(personid)s""" %
1642 sqlvalues(personid=params.subscriber.id))1643 sqlvalues(personid=params.subscriber.id))
16431644
1645 if params.structural_subscriber is not None:
1646 structural_subscriber_clause = ( """BugTask.id IN (
1647 SELECT BugTask.id FROM BugTask, StructuralSubscription
1648 WHERE BugTask.product = StructuralSubscription.product
1649 AND StructuralSubscription.subscriber = %(personid)s
1650 UNION ALL
1651 SELECT BugTask.id FROM BugTask, StructuralSubscription
1652 WHERE
1653 BugTask.distribution = StructuralSubscription.distribution
1654 AND BugTask.sourcepackagename =
1655 StructuralSubscription.sourcepackagename
1656 AND StructuralSubscription.subscriber = %(personid)s
1657 UNION ALL
1658 SELECT BugTask.id FROM BugTask, StructuralSubscription
1659 WHERE
1660 BugTask.distroseries = StructuralSubscription.distroseries
1661 AND StructuralSubscription.subscriber = %(personid)s
1662 UNION ALL
1663 SELECT BugTask.id FROM BugTask, StructuralSubscription
1664 WHERE
1665 BugTask.milestone = StructuralSubscription.milestone
1666 AND StructuralSubscription.subscriber = %(personid)s
1667 UNION ALL
1668 SELECT BugTask.id FROM BugTask, StructuralSubscription
1669 WHERE
1670 BugTask.productseries = StructuralSubscription.productseries
1671 AND StructuralSubscription.subscriber = %(personid)s
1672 UNION ALL
1673 SELECT BugTask.id FROM BugTask, StructuralSubscription, Product
1674 WHERE
1675 BugTask.product = Product.id
1676 AND Product.project = StructuralSubscription.project
1677 AND StructuralSubscription.subscriber = %(personid)s
1678 UNION ALL
1679 SELECT BugTask.id FROM BugTask, StructuralSubscription
1680 WHERE
1681 BugTask.distribution = StructuralSubscription.distribution
1682 AND StructuralSubscription.sourcepackagename is NULL
1683 AND StructuralSubscription.subscriber = %(personid)s)""" %
1684 sqlvalues(personid=params.structural_subscriber))
1685 extra_clauses.append(structural_subscriber_clause)
1686
1644 if params.component:1687 if params.component:
1645 clauseTables += ["SourcePackagePublishingHistory",1688 clauseTables += ["SourcePackagePublishingHistory",
1646 "SourcePackageRelease"]1689 "SourcePackageRelease"]
16471690
=== modified file 'lib/lp/bugs/stories/patches-view/patches-view.txt'
--- lib/lp/bugs/stories/patches-view/patches-view.txt 2010-04-16 13:01:51 +0000
+++ lib/lp/bugs/stories/patches-view/patches-view.txt 2010-06-07 18:13:25 +0000
@@ -431,6 +431,100 @@
431 Tabs: ...431 Tabs: ...
432 Main heading: Patch attachments for Patchy Person432 Main heading: Patch attachments for Patchy Person
433433
434The patches view for a person or team will also show patches to which a person
435or team is structurally subscribed. Structural subscription searching is
436thoroughly tested in bugtask-search.txt so here a single structural
437subscription will be tested.
438
439 >>> from canonical.launchpad.ftests import login, logout
440 >>> project_subscriber = factory.doAsUser(
441 ... 'foo.bar@canonical.com', factory.makePerson,
442 ... name="project-subscriber", displayname="Project Subscriber")
443 >>> login('foo.bar@canonical.com')
444 >>> patchy_product.addBugSubscription(project_subscriber,
445 ... project_subscriber)
446 <StructuralSubscription at ...>
447 >>> logout()
448 >>> from zope.security.proxy import removeSecurityProxy
449 >>> subscriber_name = removeSecurityProxy(project_subscriber).name
450 >>> anon_browser.open(
451 ... 'http://bugs.launchpad.dev/~%s/+patches' % subscriber_name)
452 >>> show_patches_view(anon_browser.contents)
453 Bug Importance Status Project Patch Age
454 Bug #18: bug_c title Wishlist Fix Committed patchy-product-1 ...second...
455 From: Patchy Person
456 Link: patch_f.diff
457 description of patch f
458 Bug #17: bug_b title Critical Confirmed patchy-product-1 ...second...
459 From: Patchy Person
460 Link: patch_c.diff
461 description of patch c
462 Bug #16: bug_a title Undecided New patchy-product-1 ...second...
463 From: Patchy Person
464 Link: patch_a.diff
465 description of patch a
466
467To ensure that all bugs with patches are shown for every structural
468subscription a new bug and is made to affect the evolution source package for
469Ubuntu. Additionally, a patch is added to that bug report so that it will
470show up in the +patches view. Project-subscriber is then subscribed to the
471evolution source package for Ubuntu and it is tested that both groups of
472patches are shown.
473
474 >>> hacky_product = factory.doAsUser(
475 ... 'foo.bar@canonical.com', factory.makeProduct,
476 ... name='hacky-product', displayname="Hacky Product")
477 >>> transaction.commit()
478 >>> bug_e = factory.doAsUser(
479 ... 'foo.bar@canonical.com', make_bug,
480 ... title="bug_e is for evolution", product=hacky_product,
481 ... importance=BugTaskImportance.WISHLIST,
482 ... status=BugTaskStatus.NEW)
483 >>> factory.doAsUser(
484 ... 'foo.bar@canonical.com', factory.makeBugAttachment,
485 ... comment="comment about patch h",
486 ... filename="patch_h.diff", owner=patch_submitter,
487 ... description="patch h is for helping", bug=bug_e, is_patch=True)
488 <BugAttachment at...
489 >>> factory.doAsUser(
490 ... 'foo.bar@canonical.com', make_bugtask, bug=bug_e,
491 ... target='evolution', target_is_spkg_name=True,
492 ... importance=BugTaskImportance.MEDIUM,
493 ... status=BugTaskStatus.TRIAGED)
494 >>> transaction.commit()
495 >>> from canonical.launchpad.ftests import login, logout
496 >>> from zope.component import getUtility
497 >>> from lp.registry.interfaces.distribution import IDistributionSet
498 >>> login('foo.bar@canonical.com')
499 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
500 >>> ubuntu_evolution = ubuntu.getSourcePackage("evolution")
501 >>> ubuntu_evolution.addBugSubscription(project_subscriber,
502 ... project_subscriber)
503 <StructuralSubscription at ...>
504 >>> logout()
505 >>> from zope.security.proxy import removeSecurityProxy
506 >>> subscriber_name = removeSecurityProxy(project_subscriber).name
507 >>> anon_browser.open(
508 ... 'http://bugs.launchpad.dev/~%s/+patches' % subscriber_name)
509 >>> show_patches_view(anon_browser.contents)
510 Bug Importance Status Project Patch Age
511 Bug #20: bug_e is... Medium Triaged evolution ...second...
512 From: Patchy Person
513 Link: patch_h.diff
514 patch h is for helping
515 Bug #18: bug_c title Wishlist Fix Committed patchy-product-1 ...second...
516 From: Patchy Person
517 Link: patch_f.diff
518 description of patch f
519 Bug #17: bug_b title Critical Confirmed patchy-product-1 ...second...
520 From: Patchy Person
521 Link: patch_c.diff
522 description of patch c
523 Bug #16: bug_a title Undecided New patchy-product-1 ...second...
524 From: Patchy Person
525 Link: patch_a.diff
526 description of patch a
527
434Reaching the Patches View528Reaching the Patches View
435-------------------------529-------------------------
436530
@@ -496,24 +590,28 @@
496590
497 >>> print_bugfilters_portlet_filled(anon_browser, 'ubuntu')591 >>> print_bugfilters_portlet_filled(anon_browser, 'ubuntu')
498 6 New bugs592 6 New bugs
499 9 Open bugs593 10 Open bugs
500 0 In-progress bugs594 0 In-progress bugs
501 0 Critical bugs595 0 Critical bugs
502 1 High importance bug596 1 High importance bug
503 0 Incomplete bugs (can expire)597 0 Incomplete bugs (can expire)
504 <BLANKLINE>598 <BLANKLINE>
505 5 Bugs with patches599 6 Bugs with patches
506 2 Bugs fixed elsewhere600 2 Bugs fixed elsewhere
507 2 Open CVE bugs - CVE reports601 2 Open CVE bugs - CVE reports
508602
509The number of bugs with patches shown in the bugfilter stats portlet603The number of bugs with patches shown in the bugfilter stats portlet
510might be lower than the number of bugs (bugtasks) listed on the604might be lower than the number of bugs (bugtasks) listed on the
511corresponding patches view page, because the latter shows resolved605corresponding patches view page, because the latter shows resolved
512bugs too.606bugs too. N.B. only 5 bug tasks will appear at a time.
513607
514 >>> anon_browser.open('http://bugs.launchpad.dev/ubuntu/+patches')608 >>> anon_browser.open('http://bugs.launchpad.dev/ubuntu/+patches')
515 >>> show_patches_view(anon_browser.contents)609 >>> show_patches_view(anon_browser.contents)
516 Bug Importance Status Package Patch Age610 Bug Importance Status Package Patch Age
611 Bug #20: bug_e... Medium Triaged evolution ...second...
612 From: Patchy Person
613 Link: patch_h.diff
614 patch h is for helping
517 Bug #18: bug_c title High Triaged a52dec ...second...615 Bug #18: bug_c title High Triaged a52dec ...second...
518 From: Patchy Person616 From: Patchy Person
519 Link: patch_f.diff617 Link: patch_f.diff
@@ -530,7 +628,3 @@
530 From: Patchy Person628 From: Patchy Person
531 Link: patch_a.diff629 Link: patch_a.diff
532 description of patch a630 description of patch a
533 Bug #16: bug_a title Undecided New ubuntu ...second...
534 From: Patchy Person
535 Link: patch_a.diff
536 description of patch a
537631
=== modified file 'lib/lp/bugs/stories/structural-subscriptions/xx-bug-subscriptions.txt'
--- lib/lp/bugs/stories/structural-subscriptions/xx-bug-subscriptions.txt 2009-10-28 18:00:36 +0000
+++ lib/lp/bugs/stories/structural-subscriptions/xx-bug-subscriptions.txt 2010-06-07 18:13:25 +0000
@@ -64,7 +64,7 @@
64 Landscape Developers64 Landscape Developers
6565
6666
67== Additional options for distribuion drivers ==67== Additional options for distribution drivers ==
6868
69When editing the subscriptions for a package, a distribution driver69When editing the subscriptions for a package, a distribution driver
70can subscribe any Launchpad user.70can subscribe any Launchpad user.
7171
=== modified file 'lib/lp/bugs/stories/webservice/xx-bug.txt'
--- lib/lp/bugs/stories/webservice/xx-bug.txt 2010-05-18 12:38:19 +0000
+++ lib/lp/bugs/stories/webservice/xx-bug.txt 2010-06-07 18:13:25 +0000
@@ -1660,7 +1660,7 @@
1660 owner_link: u'http://api.launchpad.dev/beta/~testuser1'1660 owner_link: u'http://api.launchpad.dev/beta/~testuser1'
1661 ...1661 ...
16621662
1663`testuser2` is subscribed to `testbugs2`, so this bug is related to this1663`testuser2` is subscribed to `testbug2`, so this bug is related to this
1664user:1664user:
16651665
1666 >>> related = webservice.named_get(1666 >>> related = webservice.named_get(
@@ -1682,15 +1682,16 @@
1682 total_size: 01682 total_size: 0
1683 ---1683 ---
16841684
1685You are not allowed to overwrite all user related parameter in the same1685You are not allowed to overwrite all user related parameters in the same
1686query, because in this case this bug will no be related to the person1686query, because this bug will not be related to the person anymore. In this
1687anymore. In this case a `400 Bad Request`-Error will be returned1687case a `400 Bad Request`-Error will be returned.
16881688
1689 >>> name12 = webservice.get("/~name12").jsonBody()1689 >>> name12 = webservice.get("/~name12").jsonBody()
1690 >>> print webservice.named_get(1690 >>> print webservice.named_get(
1691 ... '/~name16', 'searchTasks', assignee=name12['self_link'],1691 ... '/~name16', 'searchTasks', assignee=name12['self_link'],
1692 ... owner=name12['self_link'], bug_subscriber=name12['self_link'],1692 ... owner=name12['self_link'], bug_subscriber=name12['self_link'],
1693 ... bug_commenter=name12['self_link']1693 ... bug_commenter=name12['self_link'],
1694 ... structural_subscriber=name12['self_link']
1694 ... )1695 ... )
1695 HTTP/1.1 400 Bad Request...1696 HTTP/1.1 400 Bad Request...
16961697
16971698
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2010-05-07 19:07:28 +0000
+++ lib/lp/registry/tests/test_person.py 2010-06-07 18:13:25 +0000
@@ -526,7 +526,8 @@
526526
527 def checkUserFields(527 def checkUserFields(
528 self, params, assignee=None, bug_subscriber=None,528 self, params, assignee=None, bug_subscriber=None,
529 owner=None, bug_commenter=None, bug_reporter=None):529 owner=None, bug_commenter=None, bug_reporter=None,
530 structural_subscriber=None):
530 self.failUnlessEqual(assignee, params.assignee)531 self.failUnlessEqual(assignee, params.assignee)
531 # fromSearchForm() takes a bug_subscriber parameter, but saves532 # fromSearchForm() takes a bug_subscriber parameter, but saves
532 # it as subscriber on the parameter object.533 # it as subscriber on the parameter object.
@@ -534,14 +535,15 @@
534 self.failUnlessEqual(owner, params.owner)535 self.failUnlessEqual(owner, params.owner)
535 self.failUnlessEqual(bug_commenter, params.bug_commenter)536 self.failUnlessEqual(bug_commenter, params.bug_commenter)
536 self.failUnlessEqual(bug_reporter, params.bug_reporter)537 self.failUnlessEqual(bug_reporter, params.bug_reporter)
538 self.failUnlessEqual(structural_subscriber, params.structural_subscriber)
537539
538 def test_get_related_bugtasks_search_params(self):540 def test_get_related_bugtasks_search_params(self):
539 # With no specified options, get_related_bugtasks_search_params()541 # With no specified options, get_related_bugtasks_search_params()
540 # returns 4 BugTaskSearchParams objects, each with a different542 # returns 5 BugTaskSearchParams objects, each with a different
541 # user field set.543 # user field set.
542 search_params = get_related_bugtasks_search_params(544 search_params = get_related_bugtasks_search_params(
543 self.user, self.context)545 self.user, self.context)
544 self.assertEqual(len(search_params), 4)546 self.assertEqual(len(search_params), 5)
545 self.checkUserFields(547 self.checkUserFields(
546 search_params[0], assignee=self.context)548 search_params[0], assignee=self.context)
547 self.checkUserFields(549 self.checkUserFields(
@@ -550,13 +552,15 @@
550 search_params[2], owner=self.context, bug_reporter=self.context)552 search_params[2], owner=self.context, bug_reporter=self.context)
551 self.checkUserFields(553 self.checkUserFields(
552 search_params[3], bug_commenter=self.context)554 search_params[3], bug_commenter=self.context)
555 self.checkUserFields(
556 search_params[4], structural_subscriber=self.context)
553557
554 def test_get_related_bugtasks_search_params_with_assignee(self):558 def test_get_related_bugtasks_search_params_with_assignee(self):
555 # With assignee specified, get_related_bugtasks_search_params()559 # With assignee specified, get_related_bugtasks_search_params()
556 # returns 3 BugTaskSearchParams objects.560 # returns 4 BugTaskSearchParams objects.
557 search_params = get_related_bugtasks_search_params(561 search_params = get_related_bugtasks_search_params(
558 self.user, self.context, assignee=self.user)562 self.user, self.context, assignee=self.user)
559 self.assertEqual(len(search_params), 3)563 self.assertEqual(len(search_params), 4)
560 self.checkUserFields(564 self.checkUserFields(
561 search_params[0], assignee=self.user, bug_subscriber=self.context)565 search_params[0], assignee=self.user, bug_subscriber=self.context)
562 self.checkUserFields(566 self.checkUserFields(
@@ -564,19 +568,23 @@
564 bug_reporter=self.context)568 bug_reporter=self.context)
565 self.checkUserFields(569 self.checkUserFields(
566 search_params[2], assignee=self.user, bug_commenter=self.context)570 search_params[2], assignee=self.user, bug_commenter=self.context)
571 self.checkUserFields(
572 search_params[3], assignee=self.user, structural_subscriber=self.context)
567573
568 def test_get_related_bugtasks_search_params_with_owner(self):574 def test_get_related_bugtasks_search_params_with_owner(self):
569 # With owner specified, get_related_bugtasks_search_params() returns575 # With owner specified, get_related_bugtasks_search_params() returns
570 # 3 BugTaskSearchParams objects.576 # 4 BugTaskSearchParams objects.
571 search_params = get_related_bugtasks_search_params(577 search_params = get_related_bugtasks_search_params(
572 self.user, self.context, owner=self.user)578 self.user, self.context, owner=self.user)
573 self.assertEqual(len(search_params), 3)579 self.assertEqual(len(search_params), 4)
574 self.checkUserFields(580 self.checkUserFields(
575 search_params[0], owner=self.user, assignee=self.context)581 search_params[0], owner=self.user, assignee=self.context)
576 self.checkUserFields(582 self.checkUserFields(
577 search_params[1], owner=self.user, bug_subscriber=self.context)583 search_params[1], owner=self.user, bug_subscriber=self.context)
578 self.checkUserFields(584 self.checkUserFields(
579 search_params[2], owner=self.user, bug_commenter=self.context)585 search_params[2], owner=self.user, bug_commenter=self.context)
586 self.checkUserFields(
587 search_params[3], owner=self.user, structural_subscriber=self.context)
580588
581 def test_get_related_bugtasks_search_params_with_bug_reporter(self):589 def test_get_related_bugtasks_search_params_with_bug_reporter(self):
582 # With bug reporter specified, get_related_bugtasks_search_params()590 # With bug reporter specified, get_related_bugtasks_search_params()
@@ -584,7 +592,7 @@
584 # is overwritten in one instance.592 # is overwritten in one instance.
585 search_params = get_related_bugtasks_search_params(593 search_params = get_related_bugtasks_search_params(
586 self.user, self.context, bug_reporter=self.user)594 self.user, self.context, bug_reporter=self.user)
587 self.assertEqual(len(search_params), 4)595 self.assertEqual(len(search_params), 5)
588 self.checkUserFields(596 self.checkUserFields(
589 search_params[0], bug_reporter=self.user,597 search_params[0], bug_reporter=self.user,
590 assignee=self.context)598 assignee=self.context)
@@ -599,13 +607,16 @@
599 self.checkUserFields(607 self.checkUserFields(
600 search_params[3], bug_reporter=self.user,608 search_params[3], bug_reporter=self.user,
601 bug_commenter=self.context)609 bug_commenter=self.context)
610 self.checkUserFields(
611 search_params[4], bug_reporter=self.user,
612 structural_subscriber=self.context)
602613
603 def test_get_related_bugtasks_search_params_illegal(self):614 def test_get_related_bugtasks_search_params_illegal(self):
604 self.assertRaises(615 self.assertRaises(
605 IllegalRelatedBugTasksParams,616 IllegalRelatedBugTasksParams,
606 get_related_bugtasks_search_params, self.user, self.context,617 get_related_bugtasks_search_params, self.user, self.context,
607 assignee=self.user, owner=self.user, bug_commenter=self.user,618 assignee=self.user, owner=self.user, bug_commenter=self.user,
608 bug_subscriber=self.user)619 bug_subscriber=self.user, structural_subscriber=self.user)
609620
610 def test_get_related_bugtasks_search_params_illegal_context(self):621 def test_get_related_bugtasks_search_params_illegal_context(self):
611 # in case the `context` argument is not of type IPerson an622 # in case the `context` argument is not of type IPerson an