launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03835
[Merge] lp:~sinzui/launchpad/remote-bugtracker-components-ui-0 into lp:launchpad
Curtis Hovey has proposed merging lp:~sinzui/launchpad/remote-bugtracker-components-ui-0 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~sinzui/launchpad/remote-bugtracker-components-ui-0/+merge/63392
Implements UI for displaying components registered at a remote bug tracker
Launchpad bug: https://bugs.launchpad.net/bugs/617695
Pre-implementation: Bryce
This branch cleans up Bryce's branch so that it can be landed.
--------------------------------------------------------------------
RULES
* Resolve conflicts with devel.
* Moved code out the othe deprecated canonical.launchpad path.
* Fix failing tests.
* Refactor the implementation parts called out in the review that
can be implemented in a simpler way.
* ADDENDUM:
* Register feture flag bugs.bugtracker_components.enabled
* Fixed invalid storm find() clauses
* Allow users to unset the a component.
QA
* After cronscripts/update-bugzilla-remote-components.py has run
and the feature is enabled:
bugs.bugtracker_components.enabled team:launchpad-beta-testers 1 on)
http://people.canonical.com/~curtis/components.png
LINT
cronscripts/update-bugzilla-remote-components.py
lib/canonical/launchpad/interfaces/_schema_circular_imports.py
lib/lp/bugs/configure.zcml
lib/lp/bugs/browser/bugtracker.py
lib/lp/bugs/browser/configure.zcml
lib/lp/bugs/browser/tests/test_bugtracker_component.py
lib/lp/bugs/browser/widgets/bugtask.py
lib/lp/bugs/interfaces/bugtracker.py
lib/lp/bugs/model/bugtracker.py
lib/lp/bugs/scripts/bzremotecomponentfinder.py
lib/lp/bugs/templates/bugtracker-index.pt
lib/lp/bugs/templates/bugtracker-portlet-components.pt
lib/lp/bugs/tests/test_bugtracker_components.py
lib/lp/bugs/tests/test_bzremotecomponentfinder.py
lib/lp/services/features/flags.py
TEST
./bin/test -vv -t test_bugtracker_component
IMPLEMENTATION
I registered a feature flag and fixed the layout in the bug tracker template.
lib/lp/services/features/flags.py
lib/lp/bugs/templates/bugtracker-index.pt
Revised the layout of the components. This feature is born in a pathological
state; there are too many items listed for a human to read. I made the layout
use the full horizontal space, but I would not call this useable yet.
lib/lp/bugs/templates/bugtracker-portlet-components.pt
I discovered that the storm find clauses in this module were invalid. they
where assignment (=) instead or equality (==). I fixed these.
lib/lp/bugs/model/bugtracker.py
The edit form was missing a cancel link and setting the components dsp to
None caused an oops.
lib/lp/bugs/browser/bugtracker.py
Bryces's work with my fixes from the review.
cronscripts/update-bugzilla-remote-components.py
lib/canonical/launchpad/interfaces/_schema_circular_imports.py
lib/lp/bugs/configure.zcml
lib/lp/bugs/browser/configure.zcml
lib/lp/bugs/browser/tests/test_bugtracker_component.py
lib/lp/bugs/browser/widgets/bugtask.py
lib/lp/bugs/interfaces/bugtracker.py
lib/lp/bugs/scripts/bzremotecomponentfinder.py
lib/lp/bugs/tests/test_bugtracker_components.py
lib/lp/bugs/tests/test_bzremotecomponentfinder.py
--
https://code.launchpad.net/~sinzui/launchpad/remote-bugtracker-components-ui-0/+merge/63392
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/remote-bugtracker-components-ui-0 into lp:launchpad.
=== modified file 'cronscripts/update-bugzilla-remote-components.py'
--- cronscripts/update-bugzilla-remote-components.py 2010-10-19 23:55:47 +0000
+++ cronscripts/update-bugzilla-remote-components.py 2011-06-03 15:12:07 +0000
@@ -1,6 +1,6 @@
#!/usr/bin/python -S
#
-# Copyright 2010 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
# pylint: disable-msg=W0403
@@ -10,7 +10,7 @@
from canonical.config import config
from lp.services.scripts.base import LaunchpadCronScript
-from canonical.launchpad.scripts.bzremotecomponentfinder import (
+from lp.bugs.scripts.bzremotecomponentfinder import (
BugzillaRemoteComponentFinder,
)
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-06-01 20:12:45 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-06-03 15:12:07 +0000
@@ -618,6 +618,9 @@
IBugTracker, 'addRemoteComponentGroup', IBugTrackerComponentGroup)
patch_collection_return_type(
IBugTracker, 'getAllRemoteComponentGroups', IBugTrackerComponentGroup)
+patch_entry_return_type(
+ IBugTracker, 'getRemoteComponentForDistroSourcePackageName',
+ IBugTrackerComponent)
## IBugTrackerComponent
patch_reference_property(
=== modified file 'lib/lp/bugs/browser/bugtracker.py'
--- lib/lp/bugs/browser/bugtracker.py 2011-05-27 21:12:25 +0000
+++ lib/lp/bugs/browser/bugtracker.py 2011-06-03 15:12:07 +0000
@@ -10,6 +10,7 @@
'BugTrackerBreadcrumb',
'BugTrackerComponentGroupNavigation',
'BugTrackerEditView',
+ 'BugTrackerEditComponentView',
'BugTrackerNavigation',
'BugTrackerNavigationMenu',
'BugTrackerSetBreadcrumb',
@@ -65,9 +66,13 @@
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
from lp.app.widgets.textwidgets import DelimitedListWidget
+from lp.bugs.browser.widgets.bugtask import (
+ UbuntuSourcePackageNameWidget,
+ )
from lp.bugs.interfaces.bugtracker import (
BugTrackerType,
IBugTracker,
+ IBugTrackerComponent,
IBugTrackerComponentGroup,
IBugTrackerSet,
IRemoteBug,
@@ -227,6 +232,11 @@
return shortlist(chain(self.context.projects,
self.context.products), 100)
+ @property
+ def related_component_groups(self):
+ """All component groups and components."""
+ return self.context.getAllRemoteComponentGroups()
+
BUG_TRACKER_ACTIVE_VOCABULARY = SimpleVocabulary.fromItems(
[('On', True), ('Off', False)])
@@ -445,16 +455,89 @@
return RemoteBug(self.context, remotebug, bugs)
@stepthrough("+components")
- def component_groups(self, name):
- return self.context.getRemoteComponentGroup(name)
+ def component_groups(self, id):
+ # Navigate by id (component group name should work too)
+ return self.context.getRemoteComponentGroup(id)
+
+
+class BugTrackerEditComponentView(LaunchpadEditFormView):
+ """Provides editing form for setting source packages for components.
+
+ In this class we assume that bug tracker components are always
+ linked to source packages in the Ubuntu distribution.
+ """
+ schema = IBugTrackerComponent
+ custom_widget('sourcepackagename', UbuntuSourcePackageNameWidget)
+ field_names = ['sourcepackagename']
+ page_title = 'Link component'
+
+ @property
+ def label(self):
+ return (
+ 'Link a distribution source package to %s component' %
+ self.context.name)
+
+ @property
+ def initial_values(self):
+ """See `LaunchpadFormView.`"""
+ field_values = dict(sourcepackagename='')
+ dsp = self.context.distro_source_package
+ if dsp is not None:
+ field_values['sourcepackagename'] = dsp.name
+ return field_values
+
+ @property
+ def next_url(self):
+ return canonical_url(self.context.component_group.bug_tracker)
+
+ cancel_url = next_url
+
+ def updateContextFromData(self, data, context=None):
+ """Link component to specified distro source package.
+
+ Get the user-provided source package name from the form widget,
+ look it up in Ubuntu to retrieve the distro_source_package
+ object, and link it to this component.
+ """
+ sourcepackagename = data['sourcepackagename']
+ distribution = self.widgets['sourcepackagename'].getDistribution()
+ dsp = distribution.getSourcePackage(sourcepackagename)
+ bug_tracker = self.context.component_group.bug_tracker
+ # Has this source package already been assigned to a component?
+ component = bug_tracker.getRemoteComponentForDistroSourcePackageName(
+ distribution, sourcepackagename)
+ if component is not None:
+ self.request.response.addNotification(
+ "The %s source package is already linked to %s:%s in %s." % (
+ sourcepackagename.name,
+ component.component_group.name,
+ component.name, distribution.name))
+ return
+ # The submitted component can be linked to the distro source package.
+ component = context or self.context
+ component.distro_source_package = dsp
+ if sourcepackagename is None:
+ self.request.response.addNotification(
+ "%s:%s is now unlinked." % (
+ component.component_group.name, component.name))
+ else:
+ self.request.response.addNotification(
+ "%s:%s is now linked to the %s source package in %s." % (
+ component.component_group.name, component.name,
+ sourcepackagename.name, distribution.name))
+
+ @action('Save Changes', name='save')
+ def save_action(self, action, data):
+ """Update the component with the form data."""
+ self.updateContextFromData(data)
class BugTrackerComponentGroupNavigation(Navigation):
usedfor = IBugTrackerComponentGroup
- def traverse(self, name):
- return self.context.getComponent(name)
+ def traverse(self, id):
+ return self.context.getComponent(id)
class BugTrackerSetBreadcrumb(Breadcrumb):
=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml 2011-05-27 18:10:50 +0000
+++ lib/lp/bugs/browser/configure.zcml 2011-06-03 15:12:07 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2010-2011 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -813,6 +813,12 @@
path_expression="name"
attribute_to_parent="component_group"
rootsite="bugs"/>
+ <browser:page
+ name="+edit"
+ for="lp.bugs.interfaces.bugtracker.IBugTrackerComponent"
+ class="lp.bugs.browser.bugtracker.BugTrackerEditComponentView"
+ permission="launchpad.AnyPerson"
+ template="../../app/templates/generic-edit.pt"/>
<browser:pages
for="lp.bugs.interfaces.bugtracker.IBugTracker"
class="lp.bugs.browser.bugtracker.BugTrackerView"
@@ -824,6 +830,9 @@
name="+portlet-details"
template="../templates/bugtracker-portlet-details.pt"/>
<browser:page
+ name="+portlet-components"
+ template="../templates/bugtracker-portlet-components.pt"/>
+ <browser:page
name="+portlet-projects"
template="../templates/bugtracker-portlet-projects.pt"/>
<browser:page
=== added file 'lib/lp/bugs/browser/tests/test_bugtracker_component.py'
--- lib/lp/bugs/browser/tests/test_bugtracker_component.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/browser/tests/test_bugtracker_component.py 2011-06-03 15:12:07 +0000
@@ -0,0 +1,124 @@
+# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version (see the file LICENSE).
+
+"""Unit tests for linking bug tracker components to source packages."""
+
+__metaclass__ = type
+
+from zope.component import getUtility
+
+from canonical.launchpad.webapp.publisher import canonical_url
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.registry.interfaces.distribution import IDistributionSet
+from lp.testing import (
+ login_person,
+ TestCaseWithFactory,
+ )
+from lp.testing.views import create_initialized_view
+
+
+class BugTrackerEditComponentViewTextCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(BugTrackerEditComponentViewTextCase, self).setUp()
+ regular_user = self.factory.makePerson()
+ login_person(regular_user)
+
+ self.bug_tracker = self.factory.makeBugTracker()
+ self.comp_group = self.factory.makeBugTrackerComponentGroup(
+ u'alpha', self.bug_tracker)
+
+ def _makeForm(self, sourcepackage):
+ if sourcepackage is None:
+ name = ''
+ else:
+ name = sourcepackage.name
+ return {
+ 'field.sourcepackagename': name,
+ 'field.actions.save': 'Save',
+ }
+
+ def test_view_attributes(self):
+ component = self.factory.makeBugTrackerComponent(
+ u'Example', self.comp_group)
+ distro = getUtility(IDistributionSet).getByName('ubuntu')
+ package = self.factory.makeDistributionSourcePackage(
+ sourcepackagename='example', distribution=distro)
+ form = self._makeForm(package)
+ view = create_initialized_view(
+ component, name='+edit', form=form)
+ label = 'Link a distribution source package to Example component'
+ self.assertEqual(label, view.label)
+ self.assertEqual('Link component', view.page_title)
+ self.assertEqual(['sourcepackagename'], view.field_names)
+ url = canonical_url(component.component_group.bug_tracker)
+ self.assertEqual(url, view.next_url)
+ self.assertEqual(url, view.cancel_url)
+
+ def test_linking(self):
+ component = self.factory.makeBugTrackerComponent(
+ u'Example', self.comp_group)
+ distro = getUtility(IDistributionSet).getByName('ubuntu')
+ package = self.factory.makeDistributionSourcePackage(
+ sourcepackagename='example', distribution=distro)
+
+ self.assertIs(None, component.distro_source_package)
+ form = self._makeForm(package)
+ view = create_initialized_view(
+ component, name='+edit', form=form)
+ self.assertEqual([], view.errors)
+
+ notifications = view.request.response.notifications
+ self.assertEqual(component.distro_source_package, package)
+ expected = (
+ u"alpha:Example is now linked to the example "
+ "source package in ubuntu.")
+ self.assertEqual(expected, notifications.pop().message)
+
+ def test_unlinking(self):
+ component = self.factory.makeBugTrackerComponent(
+ u'Example', self.comp_group)
+ distro = getUtility(IDistributionSet).getByName('ubuntu')
+ dsp = self.factory.makeDistributionSourcePackage(
+ sourcepackagename='example', distribution=distro)
+ component.distro_source_package = dsp
+ form = self._makeForm(None)
+ view = create_initialized_view(
+ component, name='+edit', form=form)
+ self.assertEqual([], view.errors)
+ notifications = view.request.response.notifications
+ self.assertEqual(None, component.distro_source_package)
+ expected = "alpha:Example is now unlinked."
+ self.assertEqual(expected, notifications.pop().message)
+
+ def test_cannot_doublelink_sourcepackages(self):
+ # Two components try linking to same package
+ component_a = self.factory.makeBugTrackerComponent(
+ u'a', self.comp_group)
+ component_b = self.factory.makeBugTrackerComponent(
+ u'b', self.comp_group)
+ distro = getUtility(IDistributionSet).getByName('ubuntu')
+ package = self.factory.makeDistributionSourcePackage(
+ sourcepackagename='example', distribution=distro)
+
+ form = self._makeForm(package)
+ view = create_initialized_view(
+ component_a, name='+edit', form=form)
+ notifications = view.request.response.notifications
+ self.assertEqual([], view.errors)
+ self.assertEqual(1, len(notifications))
+ self.assertEqual(package, component_a.distro_source_package)
+
+ form = self._makeForm(package)
+ view = create_initialized_view(
+ component_b, name='+edit', form=form)
+ self.assertIs(None, component_b.distro_source_package)
+ self.assertEqual([], view.errors)
+ notifications = view.request.response.notifications
+ self.assertEqual(1, len(notifications))
+ expected = (
+ "The example source package is already linked to "
+ "alpha:a in ubuntu.")
+ self.assertEqual(expected, notifications.pop().message)
=== modified file 'lib/lp/bugs/browser/widgets/bugtask.py'
--- lib/lp/bugs/browser/widgets/bugtask.py 2011-02-02 15:43:31 +0000
+++ lib/lp/bugs/browser/widgets/bugtask.py 2011-06-03 15:12:07 +0000
@@ -14,6 +14,7 @@
"DBItemDisplayWidget",
"NewLineToSpacesWidget",
"NominationReviewActionWidget",
+ "UbuntuSourcePackageNameWidget",
]
from xml.sax.saxutils import escape
@@ -54,6 +55,7 @@
NotFoundError,
UnexpectedFormData,
)
+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.app.widgets.helpers import get_widget_template
from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
from lp.app.widgets.popup import VocabularyPickerWidget
@@ -527,6 +529,14 @@
return distribution
+class UbuntuSourcePackageNameWidget(BugTaskSourcePackageNameWidget):
+ """A widget to select Ubuntu packages."""
+
+ def getDistribution(self):
+ """See `BugTaskSourcePackageNameWidget`"""
+ return getUtility(ILaunchpadCelebrities).ubuntu
+
+
class AssigneeDisplayWidget(BrowserWidget):
"""A widget for displaying an assignee."""
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2011-04-13 18:48:42 +0000
+++ lib/lp/bugs/configure.zcml 2011-06-03 15:12:07 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009-2010 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2009-2011 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -394,6 +394,7 @@
getBugFilingAndSearchLinks
getBugsWatching
getLinkedPersonByName
+ getRemoteComponentForDistroSourcePackageName
getRemoteComponentGroup
has_lp_plugin
id
=== modified file 'lib/lp/bugs/interfaces/bugtracker.py'
--- lib/lp/bugs/interfaces/bugtracker.py 2011-02-23 20:26:53 +0000
+++ lib/lp/bugs/interfaces/bugtracker.py 2011-06-03 15:12:07 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
# pylint: disable-msg=E0211,E0213
@@ -33,6 +33,7 @@
export_read_operation,
export_write_operation,
exported,
+ operation_for_version,
operation_parameters,
operation_returns_collection_of,
operation_returns_entry,
@@ -390,7 +391,8 @@
@operation_parameters(
component_group_name=TextLine(
- title=u"The name of the remote component group", required=True))
+ title=u"The name of the remote component group",
+ required=True))
@operation_returns_entry(Interface)
@export_read_operation()
def getRemoteComponentGroup(component_group_name):
@@ -399,6 +401,23 @@
:param component_group_name: Name of the component group to retrieve.
"""
+ @operation_parameters(
+ distribution=TextLine(
+ title=u"The distribution for the source package",
+ required=True),
+ sourcepackagename=TextLine(
+ title=u"The source package name",
+ required=True))
+ @operation_returns_entry(Interface)
+ @export_read_operation()
+ @operation_for_version('devel')
+ def getRemoteComponentForDistroSourcePackageName(
+ distribution, sourcepackagename):
+ """Returns the component linked to this source package, if any.
+
+ If no components have been linked, returns value of None.
+ """
+
class IBugTrackerSet(Interface):
"""A set of IBugTracker's.
@@ -538,6 +557,10 @@
title=_('Name'),
description=_("The name of a software component "
"as shown in Launchpad.")))
+ sourcepackagename = Choice(
+ title=_("Package"), required=False, vocabulary='SourcePackageName')
+ distribution = Choice(
+ title=_("Distribution"), required=False, vocabulary='Distribution')
distro_source_package = exported(
Reference(
=== modified file 'lib/lp/bugs/model/bugtracker.py'
--- lib/lp/bugs/model/bugtracker.py 2011-05-28 04:09:11 +0000
+++ lib/lp/bugs/model/bugtracker.py 2011-06-03 15:12:07 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
# pylint: disable-msg=E0611,W0212
@@ -271,10 +271,17 @@
if component_name is None:
return None
+ elif component_name.isdigit():
+ component_id = int(component_name)
+ return Store.of(self).find(
+ BugTrackerComponent,
+ BugTrackerComponent.id == component_id,
+ BugTrackerComponent.component_group == self.id).one()
else:
return Store.of(self).find(
BugTrackerComponent,
- (BugTrackerComponent.name == component_name)).one()
+ BugTrackerComponent.name == component_name,
+ BugTrackerComponent.component_group == self.id).one()
def addCustomComponent(self, component_name):
"""Adds a component locally that isn't synced from a remote tracker
@@ -680,11 +687,31 @@
"""See `IBugTracker`."""
component_group = None
store = IStore(BugTrackerComponentGroup)
- component_group = store.find(
- BugTrackerComponentGroup,
- name = component_group_name).one()
+ if component_group_name.isdigit():
+ component_group_id = int(component_group_name)
+ component_group = store.find(
+ BugTrackerComponentGroup,
+ BugTrackerComponentGroup.id == component_group_id).one()
+ else:
+ component_group = store.find(
+ BugTrackerComponentGroup,
+ BugTrackerComponentGroup.name == component_group_name).one()
return component_group
+ def getRemoteComponentForDistroSourcePackageName(
+ self, distribution, sourcepackagename):
+ """See `IBugTracker`."""
+ if distribution is None:
+ return None
+ dsp = distribution.getSourcePackage(sourcepackagename)
+ if dsp is None:
+ return None
+ return Store.of(self).find(
+ BugTrackerComponent,
+ BugTrackerComponent.distribution == distribution.id,
+ BugTrackerComponent.source_package_name ==
+ dsp.sourcepackagename.id).one()
+
class BugTrackerSet:
"""Implements IBugTrackerSet for a container or set of BugTrackers,
@@ -751,7 +778,7 @@
# Without context, cannot tell what store flavour is desirable.
store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
if active is not None:
- clauses = [BugTracker.active==active]
+ clauses = [BugTracker.active == active]
else:
clauses = []
results = store.find(BugTracker, *clauses)
=== renamed file 'lib/canonical/launchpad/scripts/bzremotecomponentfinder.py' => 'lib/lp/bugs/scripts/bzremotecomponentfinder.py'
--- lib/canonical/launchpad/scripts/bzremotecomponentfinder.py 2010-12-20 03:21:03 +0000
+++ lib/lp/bugs/scripts/bzremotecomponentfinder.py 2011-06-03 15:12:07 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Utilities for the update-bugzilla-remote-components cronscript"""
@@ -47,7 +47,7 @@
def __init__(self, base_url=None):
self.base_url = re.sub(r'/$', '', base_url)
- self.url = "%s/query.cgi?format=advanced" %(self.base_url)
+ self.url = "%s/query.cgi?format=advanced" % (self.base_url)
self.products = {}
def getPage(self):
@@ -117,7 +117,6 @@
self.static_bugzilla_text = static_bugzilla_text
def getRemoteProductsAndComponents(self, bugtracker_name=None):
- """"""
lp_bugtrackers = getUtility(IBugTrackerSet)
if bugtracker_name is not None:
lp_bugtrackers = [
@@ -133,10 +132,10 @@
if lp_bugtracker.name in self._BLACKLIST:
continue
- self.logger.info("%s: %s" %(
+ self.logger.info("%s: %s" % (
lp_bugtracker.name, lp_bugtracker.baseurl))
bz_bugtracker = BugzillaRemoteComponentScraper(
- base_url = lp_bugtracker.baseurl)
+ base_url=lp_bugtracker.baseurl)
if self.static_bugzilla_text is not None:
self.logger.debug("Using static bugzilla text")
@@ -188,10 +187,10 @@
# added to launchpad. Record them for now.
for component in product['components'].values():
components_to_add.append(
- "('%s', %d, 'True', 'False')" %(
+ "('%s', %d, 'True', 'False')" % (
component['name'], lp_component_group.id))
- if len(components_to_add)>0:
+ if len(components_to_add) > 0:
sqltext = """
INSERT INTO BugTrackerComponent
(name, component_group, is_visible, is_custom)
=== modified file 'lib/lp/bugs/templates/bugtracker-index.pt'
--- lib/lp/bugs/templates/bugtracker-index.pt 2010-10-10 21:54:16 +0000
+++ lib/lp/bugs/templates/bugtracker-index.pt 2011-06-03 15:12:07 +0000
@@ -39,7 +39,12 @@
<div tal:replace="structure context/@@+portlet-projects" />
</div>
</div>
- <div class="yui-u" tal:condition="context/watches">
+ <div class="yui-u"
+ tal:condition="features/bugs.bugtracker_components.enabled">
+ <div tal:replace="structure context/@@+portlet-components" />
+ </div>
+ <div class="yui-u"
+ tal:condition="context/watches">
<div tal:replace="structure context/@@+portlet-watches" />
</div>
</div>
=== added file 'lib/lp/bugs/templates/bugtracker-portlet-components.pt'
--- lib/lp/bugs/templates/bugtracker-portlet-components.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/templates/bugtracker-portlet-components.pt 2011-06-03 15:12:07 +0000
@@ -0,0 +1,43 @@
+<div
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ class="portlet" id="portlet-components"
+ tal:define="related_component_groups view/related_component_groups">
+ <h2>Components</h2>
+
+ <p>
+ You can link components from this bug tracker to their corresponding
+ distribution source packages in the project's “Change
+ components” page.
+ </p>
+
+ <p tal:condition="not: related_component_groups/count">
+ <strong>This bug tracker has no components.</strong>
+ </p>
+
+ <dl tal:condition="related_component_groups">
+ <tal:group repeat="component_group related_component_groups">
+ <dt><span tal:replace="component_group/name" />:</dt>
+ <tal:components define="components component_group/components">
+ <dd class="subordinate">
+ <ul style="margin-top: 0" class="horizontal">
+ <li style="white-space: nowrap;" tal:repeat="component components">
+ <span tal:replace="component/name" />
+ <a
+ tal:condition="component/distro_source_package"
+ tal:replace="structure component/distro_source_package/fmt:link" />
+ <a class="menu-link-edit sprite edit"
+ tal:attributes="href string:${context/fmt:url}/+components/${component_group/id}/${component/id}/+edit"><span
+ class="invisible-link">Edit component</span></a>
+ </li>
+ </ul>
+ </dd>
+ <dd class="subordinate"
+ tal:condition="not: components">
+ This bug tracker has no components for this group.
+ </dd>
+ </tal:components>
+ </tal:group>
+ </dl>
+</div>
=== modified file 'lib/lp/bugs/tests/test_bugtracker_components.py'
--- lib/lp/bugs/tests/test_bugtracker_components.py 2010-10-15 06:01:53 +0000
+++ lib/lp/bugs/tests/test_bugtracker_components.py 2011-06-03 15:12:07 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test for components and component groups (products) in bug trackers."""
@@ -7,7 +7,6 @@
__all__ = []
-import unittest
import transaction
from canonical.launchpad.ftests import login_person
@@ -21,12 +20,12 @@
)
-class TestBugTrackerComponent(TestCaseWithFactory):
+class BugTrackerComponentTestCase(TestCaseWithFactory):
layer = DatabaseFunctionalLayer
def setUp(self):
- super(TestBugTrackerComponent, self).setUp()
+ super(BugTrackerComponentTestCase, self).setUp()
regular_user = self.factory.makePerson()
login_person(regular_user)
@@ -88,14 +87,15 @@
def test_link_distro_source_package(self):
"""Check that a link can be set to a distro source package"""
- component = self.factory.makeBugTrackerComponent(
+ example_component = self.factory.makeBugTrackerComponent(
u'example', self.comp_group)
- package = self.factory.makeDistributionSourcePackage()
- self.assertIs(None, component.distro_source_package)
+ dsp = self.factory.makeDistributionSourcePackage(u'example')
- # Set the source package on the component
- component.distro_source_package = package
- self.assertIsNot(None, component.distro_source_package)
+ example_component.distro_source_package = dsp
+ self.assertEqual(dsp, example_component.distro_source_package)
+ comp = self.bug_tracker.getRemoteComponentForDistroSourcePackageName(
+ dsp.distribution, dsp.sourcepackagename)
+ self.assertIsNot(example_component, comp)
class TestBugTrackerWithComponents(TestCaseWithFactory):
@@ -148,7 +148,7 @@
def test_multiple_product_bugtracker(self):
"""Bug tracker with multiple products and components"""
# Create several component groups with varying numbers of components
- comp_group_i = self.bug_tracker.addRemoteComponentGroup(u'alpha')
+ self.bug_tracker.addRemoteComponentGroup(u'alpha')
comp_group_ii = self.bug_tracker.addRemoteComponentGroup(u'beta')
comp_group_ii.addComponent(u'example-beta-1')
@@ -291,10 +291,3 @@
component = ws_object(self.launchpad, db_comp)
package = ws_object(self.launchpad, db_src_pkg)
component.distro_source_package = package
-
-
-def test_suite():
- suite = unittest.TestSuite()
- suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
-
- return suite
=== modified file 'lib/lp/bugs/tests/test_bzremotecomponentfinder.py'
--- lib/lp/bugs/tests/test_bzremotecomponentfinder.py 2010-12-20 03:21:03 +0000
+++ lib/lp/bugs/tests/test_bzremotecomponentfinder.py 2011-06-03 15:12:07 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests cronscript for retriving components from remote Bugzillas"""
@@ -8,14 +8,13 @@
__all__ = []
import os
-import unittest
import transaction
from canonical.testing import DatabaseFunctionalLayer
from canonical.launchpad.ftests import (
login,
)
-from canonical.launchpad.scripts.bzremotecomponentfinder import (
+from lp.bugs.scripts.bzremotecomponentfinder import (
BugzillaRemoteComponentFinder,
BugzillaRemoteComponentScraper,
dictFromCSV,
@@ -105,7 +104,7 @@
# Set up remote bug tracker with synthetic data
bz_bugtracker = BugzillaRemoteComponentScraper(
- base_url = "http://bugzilla.example.org")
+ base_url="http://bugzilla.example.org")
bz_bugtracker.products = {
u'alpha': {
'name': u'alpha',
@@ -147,8 +146,8 @@
title="fdo-example",
name="fdo-example")
transaction.commit()
- bz_bugtracker = BugzillaRemoteComponentScraper(
- base_url = "http://bugzilla.example.org")
+ BugzillaRemoteComponentScraper(
+ base_url="http://bugzilla.example.org")
page_text = read_test_file("bugzilla-fdo-advanced-query.html")
finder = BugzillaRemoteComponentFinder(
@@ -165,7 +164,8 @@
self.assertIsNot(None, comp)
self.assertEqual(u'Driver/Radeon', comp.name)
-# FIXME: This takes ~9 sec to run, but mars says new testsuites need to compete in 2
+# FIXME: This takes ~9 sec to run, but mars says new testsuites need to
+# compete in 2
# def test_cronjob(self):
# """Runs the cron job to verify it executes without error"""
# import subprocess
@@ -182,9 +182,3 @@
# self.assertTrue('ERROR' not in err)
# self.assertTrue('CRITICAL' not in err)
# self.assertTrue('Exception raised' not in err)
-
-def test_suite():
- suite = unittest.TestSuite()
- suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
-
- return suite
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2011-05-25 07:21:58 +0000
+++ lib/lp/services/features/flags.py 2011-06-03 15:12:07 +0000
@@ -38,6 +38,10 @@
# NOTE: "default behaviour" does not specify a default value. It
# merely documents the code's behaviour if no value is specified.
flag_info = sorted([
+ ('bugs.bugtracker_components.enabled',
+ 'boolean',
+ ('Enables the display of bugtracker components.'),
+ ''),
('code.branchmergequeue',
'boolean',
'Enables merge queue pages and lists them on branch pages.',
Follow ups