← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~linaro-infrastructure/launchpad/team-engineering-view into lp:launchpad

 

Guilherme Salgado has proposed merging lp:~linaro-infrastructure/launchpad/team-engineering-view into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~linaro-infrastructure/launchpad/team-engineering-view/+merge/99342

Those new methods will be used by a new view showing the upcoming work assigned to members of a team (https://dev.launchpad.net/Projects/WorkItems).

Both queries do some LeftJoins to bring in all the related data in a single query and avoid hitting the DB again for every returned item.  The new filters to BugTaskSet.search() was a suggestion from Robert and we agreed to do the conjoined master filtering in python because the existing one in BugTaskSet.search() works only when you're filtering results for a single milestone.

Also, the plan is to not do batching on those pages as the page only includes stuff assigned to a team member *and* targeted to a milestone in the next few months. However, the new page will be guarded by a feature flag and we'll be conducting user testing to make sure we can avoid batching there.
-- 
https://code.launchpad.net/~linaro-infrastructure/launchpad/team-engineering-view/+merge/99342
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~linaro-infrastructure/launchpad/team-engineering-view into lp:launchpad.
=== modified file 'lib/lp/blueprints/model/specificationworkitem.py'
--- lib/lp/blueprints/model/specificationworkitem.py	2012-02-28 04:24:19 +0000
+++ lib/lp/blueprints/model/specificationworkitem.py	2012-03-26 14:53:00 +0000
@@ -47,8 +47,9 @@
 
     def __repr__(self):
         title = self.title.encode('ASCII', 'backslashreplace')
+        assignee = getattr(self.assignee, 'name', None)
         return '<SpecificationWorkItem [%s] %s: %s of %s>' % (
-            self.assignee, title, self.status, self.specification)
+            assignee, title, self.status.name, self.specification)
 
     def __init__(self, title, status, specification, assignee, milestone,
                  sequence):

=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py	2012-03-06 21:13:36 +0000
+++ lib/lp/bugs/interfaces/bugtask.py	2012-03-26 14:53:00 +0000
@@ -1185,7 +1185,8 @@
                  linked_branches=None, linked_blueprints=None,
                  structural_subscriber=None, modified_since=None,
                  created_since=None, exclude_conjoined_tasks=False, cve=None,
-                 upstream_target=None):
+                 upstream_target=None, milestone_dateexpected_before=None,
+                 milestone_dateexpected_after=None):
 
         self.bug = bug
         self.searchtext = searchtext
@@ -1236,6 +1237,8 @@
         self.exclude_conjoined_tasks = exclude_conjoined_tasks
         self.cve = cve
         self.upstream_target = upstream_target
+        self.milestone_dateexpected_before = milestone_dateexpected_before
+        self.milestone_dateexpected_after = milestone_dateexpected_after
 
     def setProduct(self, product):
         """Set the upstream context on which to filter the search."""

=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py	2012-03-25 23:33:53 +0000
+++ lib/lp/bugs/model/bug.py	2012-03-26 14:53:00 +0000
@@ -2129,7 +2129,7 @@
         """A set of known persons able to view this bug.
 
         This method must return an empty set or bug searches will trigger late
-        evaluation. Any 'should be set on load' propertis must be done by the
+        evaluation. Any 'should be set on load' properties must be done by the
         bug search.
 
         If you are tempted to change this method, don't. Instead see

=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
--- lib/lp/bugs/model/bugtasksearch.py	2012-03-20 09:43:46 +0000
+++ lib/lp/bugs/model/bugtasksearch.py	2012-03-26 14:53:00 +0000
@@ -659,6 +659,18 @@
         extra_clauses.append(nominated_for_clause)
         clauseTables.append(BugNomination)
 
+    dateexpected_before = params.milestone_dateexpected_before
+    dateexpected_after = params.milestone_dateexpected_after
+    if dateexpected_after or dateexpected_before:
+        clauseTables.append(Milestone)
+        extra_clauses.append("BugTask.milestone = Milestone.id")
+        if dateexpected_after:
+            extra_clauses.append("Milestone.dateexpected >= %s"
+                                 % sqlvalues(dateexpected_after))
+        if dateexpected_before:
+            extra_clauses.append("Milestone.dateexpected <= %s"
+                                 % sqlvalues(dateexpected_before))
+
     clause, decorator = _get_bug_privacy_filter_with_decorator(params.user)
     if clause:
         extra_clauses.append(clause)

=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py	2012-03-08 11:51:36 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py	2012-03-26 14:53:00 +0000
@@ -1572,6 +1572,36 @@
         return [bugtask.bug.id for bugtask in expected_bugtasks]
 
 
+class TestMilestoneDueDateFiltering(TestCaseWithFactory):
+
+    layer = LaunchpadFunctionalLayer
+
+    def test_milestone_date_filters(self):
+        today = datetime.today().date()
+        ten_days_ago = today - timedelta(days=10)
+        ten_days_from_now = today + timedelta(days=10)
+        current_milestone = self.factory.makeMilestone(dateexpected=today)
+        old_milestone = self.factory.makeMilestone(
+            dateexpected=ten_days_ago)
+        future_milestone = self.factory.makeMilestone(
+            dateexpected=ten_days_from_now)
+        current_milestone_bug = self.factory.makeBug(
+            milestone=current_milestone)
+        old_milestone_bug = self.factory.makeBug(milestone=old_milestone)
+        future_milestone_bug = self.factory.makeBug(
+            milestone=future_milestone)
+        # Search for bugs whose milestone.dateexpected is between yesterday
+        # and tomorrow.  This will return only the one task targeted to
+        # current_milestone.
+        params = BugTaskSearchParams(
+            user=None,
+            milestone_dateexpected_after=today - timedelta(days=1),
+            milestone_dateexpected_before=today + timedelta(days=1))
+        result = getUtility(IBugTaskSet).search(params)
+        self.assertEqual(
+            current_milestone_bug.bugtasks, list(result))
+
+
 def test_suite():
     suite = unittest.TestSuite()
     loader = unittest.TestLoader()

=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py	2012-03-02 19:53:30 +0000
+++ lib/lp/registry/interfaces/person.py	2012-03-26 14:53:00 +0000
@@ -1518,6 +1518,19 @@
         :return: a boolean.
         """
 
+    def getAssignedSpecificationWorkItemsDueBefore(date):
+        """Return SpecificationWorkItems assigned to this person (or members
+        of this team) and whose milestone is due between today and the given
+        date.
+        """
+
+    def getAssignedBugTasksDueBefore(date, user):
+        """Get all BugTasks assigned to this person (or members of this team)
+        and whose milestone is due between today and the given date.
+        """
+
+    participant_ids = List(
+        title=_("The DB IDs of this team's participants"), value_type=Int())
     active_member_count = Attribute(
         "The number of real people who are members of this team.")
     # activemembers.value_type.schema will be set to IPerson once

=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py	2012-03-25 23:33:53 +0000
+++ lib/lp/registry/model/person.py	2012-03-26 14:53:00 +0000
@@ -62,6 +62,7 @@
 from storm.expr import (
     Alias,
     And,
+    Coalesce,
     Desc,
     Exists,
     In,
@@ -127,6 +128,7 @@
     SpecificationImplementationStatus,
     SpecificationSort,
     )
+from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
 from lp.blueprints.model.specification import (
     HasSpecificationsMixin,
     Specification,
@@ -221,6 +223,7 @@
     KarmaCategory,
     KarmaTotalCache,
     )
+from lp.registry.model.milestone import Milestone
 from lp.registry.model.personlocation import PersonLocation
 from lp.registry.model.pillar import PillarName
 from lp.registry.model.sourcepackagename import SourcePackageName
@@ -290,6 +293,7 @@
     REDEEMABLE_VOUCHER_STATUSES,
     VOUCHER_STATUSES,
     )
+from lp.services.searchbuilder import any
 from lp.services.statistics.interfaces.statistic import ILaunchpadStatisticSet
 from lp.services.verification.interfaces.authtoken import LoginTokenType
 from lp.services.verification.interfaces.logintoken import ILoginTokenSet
@@ -1468,6 +1472,100 @@
         """See `IPerson`."""
         self._inTeam_cache = {}
 
+    @cachedproperty
+    def participant_ids(self):
+        """See `IPerson`."""
+        return list(Store.of(self).find(
+            TeamParticipation.personID, TeamParticipation.teamID == self.id))
+
+    def getAssignedSpecificationWorkItemsDueBefore(self, date):
+        """See `IPerson`."""
+        from lp.registry.model.person import Person
+        from lp.registry.model.product import Product
+        from lp.registry.model.distribution import Distribution
+        store = Store.of(self)
+        WorkItem = SpecificationWorkItem
+        origin = [
+            WorkItem,
+            Join(Specification, WorkItem.specification == Specification.id),
+            LeftJoin(Product, Specification.product == Product.id),
+            LeftJoin(Distribution,
+                     Specification.distribution == Distribution.id),
+            # WorkItems may not have a milestone and in that case they inherit
+            # the one from the spec.
+            Join(Milestone,
+                 Coalesce(WorkItem.milestone_id,
+                          Specification.milestoneID) == Milestone.id),
+            # WorkItems may not have an assignee and in that case they inherit
+            # the one from the spec.
+            Join(Person,
+                 Coalesce(WorkItem.assignee_id,
+                          Specification.assigneeID) == Person.id),
+            ]
+        today = datetime.today().date()
+        results = store.using(*origin).find(
+            (WorkItem, Milestone, Specification, Person, Product,
+             Distribution),
+            AND(Milestone.dateexpected <= date,
+                Milestone.dateexpected >= today,
+                Person.id.is_in(self.participant_ids))
+            )
+        for result in results:
+            yield result[0]
+
+    def getAssignedBugTasksDueBefore(self, date, user):
+        """See `IPerson`."""
+        from lp.bugs.model.bugtask import BugTask
+        from lp.registry.model.distribution import Distribution
+        from lp.registry.model.distroseries import DistroSeries
+        from lp.registry.model.productseries import ProductSeries
+        today = datetime.today().date()
+        search_params = BugTaskSearchParams(
+            user, assignee=any(*self.participant_ids),
+            milestone_dateexpected_before=date,
+            milestone_dateexpected_after=today)
+        # BugTaskSet.search() performs eager loading for
+        # Product/SourcePackageName, but we want that for the ones below as
+        # well, so we do it with prejoins.
+        prejoins = [
+            (ProductSeries, LeftJoin(
+                ProductSeries, BugTask.productseries == ProductSeries.id)),
+            (DistroSeries, LeftJoin(
+                DistroSeries, BugTask.distroseries == DistroSeries.id)),
+            (Distribution, LeftJoin(
+                Distribution, BugTask.distribution == Distribution.id)),
+            (Milestone, LeftJoin(
+                Milestone, BugTask.milestone == Milestone.id)),
+            (Person, LeftJoin(
+                Person, BugTask.assignee == Person.id)),
+            ]
+        results = getUtility(IBugTaskSet).search(
+            search_params, prejoins=prejoins)
+
+        for task in results:
+            # We skip masters (instead of slaves) from conjoined relationships
+            # because we can do that without hittind the DB, which would not
+            # be possible if we wanted to skip the slaves. The simple (but
+            # expensive) way to skip the slaves would be to skip any tasks
+            # that have a non-None .conjoined_master.
+            productseries = task.productseries
+            distroseries = task.distroseries
+            if productseries is not None and task.product is None:
+                dev_focus_id = productseries.product.development_focusID
+                if (productseries.id == dev_focus_id and
+                    task.status not in BugTask._NON_CONJOINED_STATUSES):
+                    continue
+            elif (distroseries is not None
+                  and task.sourcepackagename is not None):
+                # Distribution.currentseries is expensive to run for every
+                # bugtask (as it goes through every series of that
+                # distribution), but it's a cached property and there's only
+                # one distribution with bugs in LP, so we can afford to do
+                # it here.
+                if distroseries.distribution.currentseries == distroseries:
+                    continue
+            yield task
+
     #
     # ITeam methods
     #

=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py	2012-02-28 04:24:19 +0000
+++ lib/lp/registry/tests/test_person.py	2012-03-26 14:53:00 +0000
@@ -3,7 +3,10 @@
 
 __metaclass__ = type
 
-from datetime import datetime
+from datetime import (
+    datetime,
+    timedelta,
+    )
 
 from lazr.lifecycle.snapshot import Snapshot
 from lazr.restful.utils import smartquote
@@ -1113,3 +1116,213 @@
         self.assertContentEqual(
             [team2.teamowner],
             get_recipients(team2))
+
+
+class Test_getAssignedSpecificationWorkItemsDueBefore(TestCaseWithFactory):
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(Test_getAssignedSpecificationWorkItemsDueBefore, self).setUp()
+        self.team = self.factory.makeTeam()
+        today = datetime.today().date()
+        next_month = today + timedelta(days=30)
+        next_year = today + timedelta(days=366)
+        self.current_milestone = self.factory.makeMilestone(
+            dateexpected=next_month)
+        self.product = self.current_milestone.product
+        self.future_milestone = self.factory.makeMilestone(
+            dateexpected=next_year, product=self.product)
+
+    def test_basic(self):
+        assigned_spec = self.factory.makeSpecification(
+            assignee=self.team.teamowner, milestone=self.current_milestone,
+            product=self.product)
+        # Create a workitem with no explicit assignee/milestone. This way it
+        # will inherit the ones from the spec it belongs to.
+        workitem = self.factory.makeSpecificationWorkItem(
+            title=u'workitem 1', specification=assigned_spec)
+
+        # Create a workitem with somebody who's not a member of our team as
+        # the assignee. This workitem must not be in the list returned by
+        # getAssignedSpecificationWorkItemsDueBefore().
+        self.factory.makeSpecificationWorkItem(
+            title=u'workitem 2', specification=assigned_spec,
+            assignee=self.factory.makePerson())
+
+        # Create a workitem targeted to a milestone too far in the future.
+        # This workitem must not be in the list returned by
+        # getAssignedSpecificationWorkItemsDueBefore().
+        self.factory.makeSpecificationWorkItem(
+            title=u'workitem 3', specification=assigned_spec,
+            milestone=self.future_milestone)
+
+        workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
+            self.current_milestone.dateexpected)
+
+        self.assertEqual([workitem], list(workitems))
+
+    def test_skips_workitems_with_milestone_in_the_past(self):
+        today = datetime.today().date()
+        milestone = self.factory.makeMilestone(
+            dateexpected=today - timedelta(days=1))
+        spec = self.factory.makeSpecification(
+            assignee=self.team.teamowner, milestone=milestone,
+            product=milestone.product)
+        self.factory.makeSpecificationWorkItem(
+            title=u'workitem 1', specification=spec)
+
+        workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(today)
+
+        self.assertEqual([], list(workitems))
+
+    def test_includes_workitems_from_future_spec(self):
+        assigned_spec = self.factory.makeSpecification(
+            assignee=self.team.teamowner, milestone=self.future_milestone,
+            product=self.product)
+        # This workitem inherits the spec's milestone and that's too far in
+        # the future so it won't be in the returned list.
+        self.factory.makeSpecificationWorkItem(
+            title=u'workitem 1', specification=assigned_spec)
+        # This one, on the other hand, is explicitly targeted to the current
+        # milestone, so it is included in the returned list even though its
+        # spec is targeted to the future milestone.
+        workitem = self.factory.makeSpecificationWorkItem(
+            title=u'workitem 2', specification=assigned_spec,
+            milestone=self.current_milestone)
+
+        workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
+            self.current_milestone.dateexpected)
+
+        self.assertEqual([workitem], list(workitems))
+
+    def test_includes_workitems_from_foreign_spec(self):
+        # This spec is assigned to a person who's not a member of our team, so
+        # only the workitems that are explicitly assigned to a member of our
+        # team will be in the returned list.
+        foreign_spec = self.factory.makeSpecification(
+            assignee=self.factory.makePerson(),
+            milestone=self.current_milestone, product=self.product)
+        # This one is not explicitly assigned to anyone, so it inherits the
+        # assignee of its spec and hence is not in the returned list.
+        self.factory.makeSpecificationWorkItem(
+            title=u'workitem 1', specification=foreign_spec)
+
+        # This one, on the other hand, is explicitly assigned to the a member
+        # of our team, so it is included in the returned list even though its
+        # spec is not assigned to a member of our team.
+        workitem = self.factory.makeSpecificationWorkItem(
+            title=u'workitem 2', specification=foreign_spec,
+            assignee=self.team.teamowner)
+
+        workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
+            self.current_milestone.dateexpected)
+
+        self.assertEqual([workitem], list(workitems))
+
+
+class Test_getAssignedBugTasksDueBefore(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(Test_getAssignedBugTasksDueBefore, self).setUp()
+        self.team = self.factory.makeTeam()
+        self.today = datetime.today().date()
+
+    def _assignBugTaskToTeamOwner(self, bugtask):
+        removeSecurityProxy(bugtask).assignee = self.team.teamowner
+
+    def test_basic(self):
+        milestone = self.factory.makeMilestone(dateexpected=self.today)
+        # This bug is assigned to a team member and targeted to a milestone
+        # whose due date is before the cutoff date we pass in, so it will be
+        # included in the return of getAssignedBugTasksDueBefore().
+        milestoned_bug = self.factory.makeBug(milestone=milestone)
+        self._assignBugTaskToTeamOwner(milestoned_bug.bugtasks[0])
+        # This one is assigned to a team member but not milestoned, so it is
+        # not included in the return of getAssignedBugTasksDueBefore().
+        non_milestoned_bug = self.factory.makeBug()
+        self._assignBugTaskToTeamOwner(non_milestoned_bug.bugtasks[0])
+        # This one is milestoned but not assigned to a team member, so it is
+        # not included in the return of getAssignedBugTasksDueBefore() either.
+        non_assigned_bug = self.factory.makeBug()
+        self._assignBugTaskToTeamOwner(non_assigned_bug.bugtasks[0])
+
+        bugtasks = list(self.team.getAssignedBugTasksDueBefore(
+            self.today + timedelta(days=1), user=None))
+
+        self.assertEqual(1, len(bugtasks))
+        self.assertEqual(milestoned_bug.bugtasks[0], bugtasks[0])
+
+    def test_skips_tasks_targeted_to_old_milestones(self):
+        past_milestone = self.factory.makeMilestone(
+            dateexpected=self.today - timedelta(days=1))
+        bug = self.factory.makeBug(milestone=past_milestone)
+        self._assignBugTaskToTeamOwner(bug.bugtasks[0])
+
+        bugtasks = list(self.team.getAssignedBugTasksDueBefore(
+            self.today + timedelta(days=1), user=None))
+
+        self.assertEqual(0, len(bugtasks))
+
+    def test_skips_private_bugs_the_user_is_not_allowed_to_see(self):
+        milestone = self.factory.makeMilestone(dateexpected=self.today)
+        private_bug = removeSecurityProxy(
+            self.factory.makeBug(milestone=milestone, private=True))
+        self._assignBugTaskToTeamOwner(private_bug.bugtasks[0])
+        private_bug2 = removeSecurityProxy(
+            self.factory.makeBug(milestone=milestone, private=True))
+        self._assignBugTaskToTeamOwner(private_bug2.bugtasks[0])
+
+        with person_logged_in(private_bug2.owner):
+            bugtasks = list(self.team.getAssignedBugTasksDueBefore(
+                self.today + timedelta(days=1),
+                removeSecurityProxy(private_bug2).owner))
+
+            self.assertEqual(private_bug2.bugtasks, bugtasks)
+
+    def test_skips_distroseries_task_that_is_a_conjoined_master(self):
+        distroseries = self.factory.makeDistroSeries()
+        sourcepackagename = self.factory.makeSourcePackageName()
+        milestone = self.factory.makeMilestone(
+            distroseries=distroseries, dateexpected=self.today)
+        self.factory.makeSourcePackagePublishingHistory(
+            distroseries=distroseries, sourcepackagename=sourcepackagename)
+        bug = self.factory.makeBug(
+            milestone=milestone, sourcepackagename=sourcepackagename,
+            distribution=distroseries.distribution)
+        package = distroseries.getSourcePackage(sourcepackagename.name)
+        removeSecurityProxy(bug).addTask(bug.owner, package)
+        self.assertEqual(2, len(bug.bugtasks))
+        slave, master = bug.bugtasks
+        self._assignBugTaskToTeamOwner(master)
+        self.assertEqual(None, master.conjoined_master)
+        self.assertEqual(master, slave.conjoined_master)
+        self.assertEqual(slave.milestone, master.milestone)
+        self.assertEqual(slave.assignee, master.assignee)
+
+        bugtasks = list(self.team.getAssignedBugTasksDueBefore(
+            self.today + timedelta(days=1), user=None))
+
+        self.assertEqual([slave], bugtasks)
+
+    def test_skips_productseries_task_that_is_a_conjoined_master(self):
+        milestone = self.factory.makeMilestone(dateexpected=self.today)
+        removeSecurityProxy(milestone.product).development_focus = (
+            milestone.productseries)
+        bug = self.factory.makeBug(
+            series=milestone.productseries, milestone=milestone)
+        self.assertEqual(2, len(bug.bugtasks))
+        slave, master = bug.bugtasks
+
+        # This will cause the assignee to propagate to the other bugtask as
+        # well since they're conjoined.
+        self._assignBugTaskToTeamOwner(slave)
+        self.assertEqual(master, slave.conjoined_master)
+        self.assertEqual(slave.milestone, master.milestone)
+        self.assertEqual(slave.assignee, master.assignee)
+
+        bugtasks = list(self.team.getAssignedBugTasksDueBefore(
+            self.today + timedelta(days=1), user=None))
+
+        self.assertEqual([slave], bugtasks)

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-03-26 07:05:16 +0000
+++ lib/lp/testing/factory.py	2012-03-26 14:53:00 +0000
@@ -844,23 +844,26 @@
         return getUtility(ITranslatorSet).new(group, language, person)
 
     def makeMilestone(self, product=None, distribution=None,
-                      productseries=None, name=None, active=True):
-        if product is None and distribution is None and productseries is None:
+                      productseries=None, name=None, active=True,
+                      dateexpected=None, distroseries=None):
+        if (product is None and distribution is None and productseries is None
+            and distroseries is None):
             product = self.makeProduct()
-        if distribution is None:
+        if distribution is None and distroseries is None:
             if productseries is not None:
                 product = productseries.product
             else:
                 productseries = self.makeProductSeries(product=product)
-            distroseries = None
+        elif distroseries is None:
+            distroseries = self.makeDistroSeries(distribution=distribution)
         else:
-            distroseries = self.makeDistroSeries(distribution=distribution)
+            distribution = distroseries.distribution
         if name is None:
             name = self.getUniqueString()
         return ProxyFactory(
             Milestone(product=product, distribution=distribution,
                       productseries=productseries, distroseries=distroseries,
-                      name=name, active=active))
+                      name=name, active=active, dateexpected=dateexpected))
 
     def makeProcessor(self, family=None, name=None, title=None,
                       description=None):
@@ -1656,7 +1659,7 @@
             or distribution parameters, or the those parameters must be None.
         :param series: If set, the series.product must match the product
             parameter, or the series.distribution must match the distribution
-            parameter, or the those parameters must be None.
+            parameter, or those parameters must be None.
         :param tags: If set, the tags to be added with the bug.
         :param distribution: If set, the sourcepackagename is used as the
             default bug target.