launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01935
[Merge] lp:~bryce/launchpad/lp-617698-forwarding into lp:launchpad
Bryce Harrington has proposed merging lp:~bryce/launchpad/lp-617698-forwarding into lp:launchpad with lp:~bryce/launchpad/lp-617695-linkui as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#617698 When forwarding a bug to an external bug tracker, the component field doesn't get filled in
https://bugs.launchpad.net/bugs/617698
Implement remote_component support when forwarding bugs.
This branch is dependent on the lp:~bryce/launchpad/lp-617695-linkui so that branch should be landed first.
This causes the 'component' field to be properly filled in for the bugzilla submission page when forwarding a bug report to a remote project.
lint has been checked. ec2 test has been run and passed successfully.
--
https://code.launchpad.net/~bryce/launchpad/lp-617698-forwarding/+merge/41003
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~bryce/launchpad/lp-617698-forwarding into lp:launchpad.
=== modified file 'lib/canonical/launchpad/scripts/bzremotecomponentfinder.py'
--- lib/canonical/launchpad/scripts/bzremotecomponentfinder.py 2010-10-20 00:24:50 +0000
+++ lib/canonical/launchpad/scripts/bzremotecomponentfinder.py 2010-11-16 19:54:52 +0000
@@ -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 = [
=== modified file 'lib/canonical/widgets/bugtask.py'
--- lib/canonical/widgets/bugtask.py 2010-11-08 12:52:43 +0000
+++ lib/canonical/widgets/bugtask.py 2010-11-16 19:54:52 +0000
@@ -495,6 +495,25 @@
return distribution
+class UbuntuSourcePackageNameWidget(
+ BugTaskSourcePackageNameWidget):
+ """Package widget where the distribution can be assumed as Ubuntu
+
+ This widgets works the same as `BugTaskSourcePackageNameWidget`,
+ except that it assumes the distribution is 'ubuntu'.
+ """
+ distribution_name = "ubuntu"
+
+ def getDistribution(self):
+ """See `BugTaskSourcePackageNameWidget`"""
+ distribution = getUtility(IDistributionSet).getByName(
+ self.distribution_name)
+ if distribution is None:
+ raise UnexpectedFormData(
+ "No such distribution: %s" % self.distribution_name)
+ return distribution
+
+
class AssigneeDisplayWidget(BrowserWidget):
"""A widget for displaying an assignee."""
=== modified file 'lib/lp/bugs/browser/bugalsoaffects.py'
--- lib/lp/bugs/browser/bugalsoaffects.py 2010-09-03 03:12:39 +0000
+++ lib/lp/bugs/browser/bugalsoaffects.py 2010-11-16 19:54:52 +0000
@@ -664,8 +664,20 @@
title = bug.title
description = u"Originally reported at:\n %s\n\n%s" % (
canonical_url(bug), bug.description)
+ remote_component = ""
+ comp_group = target.bugtracker.getRemoteComponentGroup(
+ target.remote_product)
+
+ # Look up the remote component if we have the necessary info
+ if comp_group is not None and data.get('add_packaging', False):
+ package_name = self.context.target.sourcepackagename
+ for component in comp_group.components:
+ if (component.distro_source_package is not None and
+ component.distro_source_package.name == package_name):
+ remote_component = component.name
+
return target.bugtracker.getBugFilingAndSearchLinks(
- target.remote_product, title, description)
+ target.remote_product, remote_component, title, description)
class BugTrackerCreationStep(AlsoAffectsStep):
=== modified file 'lib/lp/bugs/browser/bugtracker.py'
--- lib/lp/bugs/browser/bugtracker.py 2010-10-15 08:23:19 +0000
+++ lib/lp/bugs/browser/bugtracker.py 2010-11-16 19:54:52 +0000
@@ -10,6 +10,7 @@
'BugTrackerBreadcrumb',
'BugTrackerComponentGroupNavigation',
'BugTrackerEditView',
+ 'BugTrackerEditComponentView',
'BugTrackerNavigation',
'BugTrackerNavigationMenu',
'BugTrackerSetBreadcrumb',
@@ -21,6 +22,7 @@
]
from itertools import chain
+from storm.locals import Store
from zope.app.form.browser import TextAreaWidget
from zope.component import getUtility
@@ -67,13 +69,19 @@
DelimitedListWidget,
LaunchpadRadioWidget,
)
+from canonical.widgets.bugtask import (
+ UbuntuSourcePackageNameWidget,
+ )
from lp.bugs.interfaces.bugtracker import (
BugTrackerType,
IBugTracker,
IBugTrackerSet,
IRemoteBug,
+ IBugTrackerComponent,
IBugTrackerComponentGroup,
)
+from lp.bugs.model.bugtracker import BugTrackerComponent
+from lp.registry.interfaces.distribution import IDistributionSet
from lp.services.propertycache import cachedproperty
# A set of bug tracker types for which there can only ever be one bug
@@ -229,6 +237,14 @@
return shortlist(chain(self.context.projects,
self.context.products), 100)
+ @property
+ def related_component_groups(self):
+ """Return all component groups and components
+
+ This property was created for the Related components portlet in
+ the bug tracker's page.
+ """
+ return self.context.getAllRemoteComponentGroups()
BUG_TRACKER_ACTIVE_VOCABULARY = SimpleVocabulary.fromItems(
[('On', True), ('Off', False)])
@@ -447,16 +463,113 @@
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)
+
+ @property
+ def page_title(self):
+ return smartquote(
+ u'Link a distribution source package to the %s component'
+ % self.context.name)
+
+ @property
+ def field_names(self):
+ field_names = [
+ 'sourcepackagename',
+ ]
+ return field_names
+
+ def setUpWidgets(self, context=None):
+ for field in self.form_fields:
+ if (field.custom_widget is None and
+ field.__name__ in self.custom_widgets):
+ field.custom_widget = self.custom_widgets[field.__name__]
+ self.widgets = form.setUpWidgets(
+ self.form_fields, self.prefix, self.context, self.request,
+ data=self.initial_values, adapters=self.adapters,
+ ignore_request=False)
+
+ @property
+ def initial_values(self):
+ """See `LaunchpadFormView.`"""
+ field_values = {}
+ for name in self.field_names:
+ if name == 'sourcepackagename':
+ pkg = self.context.distro_source_package
+ if pkg is not None:
+ field_values['sourcepackagename'] = pkg.name
+ else:
+ field_values['sourcepackagename'] = ""
+ else:
+ field_values[name] = getattr(self.context, name)
+
+ return field_values
+
+ 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.
+ """
+ if context is None:
+ context = self.context
+ component = context
+
+ sourcepackagename = self.request.form.get(
+ self.widgets['sourcepackagename'].name)
+
+ distro_name = self.widgets['sourcepackagename'].distribution_name
+ distribution = getUtility(IDistributionSet).getByName(distro_name)
+ pkg = distribution.getSourcePackage(sourcepackagename)
+
+ # Has this source package already been assigned to a component?
+ pkgs = Store.of(component).find(
+ BugTrackerComponent,
+ BugTrackerComponent.distribution == distribution.id,
+ BugTrackerComponent.source_package_name ==
+ pkg.sourcepackagename.id)
+ if pkgs.count()>0:
+ self.request.response.addNotification(
+ "The %s source package is already linked to %s:%s in %s" %(
+ sourcepackagename, component.component_group.name,
+ component.name, distro_name))
+ return
+
+ component.distro_source_package = pkg
+ self.request.response.addNotification(
+ "%s:%s is now linked to the %s source package in %s" %(
+ component.component_group.name, component.name,
+ sourcepackagename, distro_name))
+
+ @action('Save Changes', name='save')
+ def save_action(self, action, data):
+ """Update the component with the form data."""
+ self.updateContextFromData(data)
+
+ self.next_url = canonical_url(
+ self.context.component_group.bug_tracker)
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 2010-10-28 09:11:36 +0000
+++ lib/lp/bugs/browser/configure.zcml 2010-11-16 19:54:52 +0000
@@ -811,6 +811,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="../templates/bugtracker-edit-component.pt"/>
<browser:pages
for="lp.bugs.interfaces.bugtracker.IBugTracker"
class="lp.bugs.browser.bugtracker.BugTrackerView"
@@ -822,6 +828,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
=== modified file 'lib/lp/bugs/browser/tests/bugtask-adding-views.txt'
--- lib/lp/bugs/browser/tests/bugtask-adding-views.txt 2010-11-01 14:58:34 +0000
+++ lib/lp/bugs/browser/tests/bugtask-adding-views.txt 2010-11-16 19:54:52 +0000
@@ -615,7 +615,7 @@
>>> print_links(add_task_view.upstream_bugtracker_links)
bug_filing_url:
- ...?product=foo&short_desc=Reflow%20...&long_desc=Originally%20...
+ ...?product=foo...&short_desc=Reflow%20...&long_desc=Originally%20...
bug_search_url: ...query.cgi?product=foo&short_desc=Reflow%20problems...
If the product's `bugtracker` isn't specified its
=== 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 2010-11-16 19:54:52 +0000
@@ -0,0 +1,116 @@
+# Copyright 2010 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
+
+import unittest
+from zope.component import getUtility
+
+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 TestBugTrackerEditComponentView(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestBugTrackerEditComponentView, 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 the Example component'
+ self.assertEqual(label, view.page_title)
+ fields = ['sourcepackagename']
+ self.assertEqual(fields, view.field_names)
+
+ 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(1, len(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_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 = (
+ u"""The example source package is already linked to """
+ """alpha:b in ubuntu""")
+ self.assertEqual(expected, notifications.pop().message)
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
+
+ return suite
+
+if __name__ == '__main__':
+ unittest.TextTestRunner().run(test_suite())
=== modified file 'lib/lp/bugs/doc/bugtracker.txt'
--- lib/lp/bugs/doc/bugtracker.txt 2010-10-27 20:00:54 +0000
+++ lib/lp/bugs/doc/bugtracker.txt 2010-11-16 19:54:52 +0000
@@ -356,26 +356,26 @@
Filing a bug on the remote tracker
----------------------------------
-The IBugTracker interface defines a method, getBugFilingAndSearchLinks(),
-which returns the URLs of the bug filing form and the bug search on
-the remote bug tracker as a dict. It accepts three parameters:
-remote_product, which is the name of the product on the remote
-tracker; summary, which is the bug summary to be passed to the remote
-bug tracker for searching or filing and description, which is the full
-description of the bug. This is only passed to the bug filing form as
-it is too specific for the search form.
+The IBugTracker interface defines a method,
+getBugFilingAndSearchLinks(), which returns the URLs of the bug filing
+form and the bug search on the remote bug tracker as a dict. It accepts
+fource parameters: remote_product, which is the name of the product on
+the remote tracker; remote_component, which corresponds to the component
+field; summary, which is the bug summary to be passed to the remote bug
+tracker for searching or filing and description, which is the full
+description of the bug. This is only passed to the bug filing form as it
+is too specific for the search form.
>>> def print_links(links_dict):
... for key in sorted(links_dict):
... print "%s: %s" % (key, links_dict[key])
>>> links = mozilla_bugzilla.getBugFilingAndSearchLinks(
- ... remote_product='testproduct', summary="Foo", description="Bar")
+ ... remote_product='testproduct', remote_component='testcomponent',
+ ... summary="Foo", description="Bar")
>>> print_links(links)
- bug_filing_url:
- https://.../enter_bug.cgi?product=testproduct&short_desc=Foo&long_desc=Bar
- bug_search_url:
- https://.../query.cgi?product=testproduct&short_desc=Foo
+ bug_filing_url: https://.../enter_bug.cgi?product=testproduct&component=testcomponent&short_desc=Foo&long_desc=Bar
+ bug_search_url: https://.../query.cgi?product=testproduct&short_desc=Foo
For the RT tracker we specify a Queue in which to file a ticket.
@@ -462,7 +462,8 @@
>>> example_roundup = factory.makeBugTracker(
... 'http://roundup.example.com', BugTrackerType.ROUNDUP)
>>> links = example_roundup.getBugFilingAndSearchLinks(
- ... remote_product='testproduct', summary="Foo", description="Bar")
+ ... remote_product='testproduct', remote_component='testcomponent',
+ ... summary="Foo", description="Bar")
>>> print_links(links)
bug_filing_url: http://.../issue?@template=item&title=Foo&@note=Bar
bug_search_url: http://.../issue?@template=search&@search_text=Foo
@@ -505,7 +506,7 @@
>>> links = mozilla_bugzilla.getBugFilingAndSearchLinks(
... 'test', None, None)
>>> print_links(links)
- bug_filing_url: ...?product=test&short_desc=&long_desc=
+ bug_filing_url: ...?product=test&component=&short_desc=&long_desc=
bug_search_url: ...?product=test&short_desc=
The remote_product, summary and description values are URL-encoded to ensure
@@ -514,7 +515,7 @@
>>> links = mozilla_bugzilla.getBugFilingAndSearchLinks(
... remote_product='@test&', summary="%&", description="()")
>>> print_links(links)
- bug_filing_url: ...?product=%40test%26&short_desc=%25%26&long_desc=%28%29
+ bug_filing_url: ...?product=%40test%26&component=&short_desc=%25%26&long_desc=%28%29
bug_search_url: ...?product=%40test%26&short_desc=%25%26
getBugFilingAndSearchLinks() will also handle unicode values in the
@@ -583,8 +584,6 @@
... remote_product='testproduct', summary="Foo", description="Bar")
>>> print_links(links)
- bug_filing_url:
- http://.../enter_bug.cgi?product=testproduct&short_desc=Foo&comment=Bar
- bug_search_url:
- http://.../query.cgi?product=testproduct&short_desc=Foo
+ bug_filing_url: http://.../enter_bug.cgi?product=testproduct&component=&short_desc=Foo&comment=Bar
+ bug_search_url: http://.../query.cgi?product=testproduct&short_desc=Foo
=== modified file 'lib/lp/bugs/interfaces/bugtracker.py'
--- lib/lp/bugs/interfaces/bugtracker.py 2010-11-04 02:32:16 +0000
+++ lib/lp/bugs/interfaces/bugtracker.py 2010-11-16 19:54:52 +0000
@@ -306,12 +306,14 @@
watches_needing_update = Attribute(
"The set of bug watches that need updating.")
- def getBugFilingAndSearchLinks(remote_product, summary=None,
- description=None):
+ def getBugFilingAndSearchLinks(remote_product, remote_component=None,
+ summary=None, description=None):
"""Return the bug filing and search links for the tracker.
:param remote_product: The name of the product on which the bug
is to be filed or search for.
+ :param remote_product: The name of the component on which the bug
+ is to be filed or search for.
:param summary: The string with which to pre-filly the summary
field of the upstream bug tracker's search and bug filing forms.
:param description: The string with which to pre-filly the description
@@ -538,6 +540,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 2010-11-04 02:32:16 +0000
+++ lib/lp/bugs/model/bugtracker.py 2010-11-16 19:54:52 +0000
@@ -249,7 +249,6 @@
def addComponent(self, component_name):
"""Adds a component that is synced from a remote bug tracker"""
-
component = BugTrackerComponent()
component.name = component_name
component.component_group = self
@@ -266,18 +265,23 @@
None is returned if there is no component by that name in the
group.
"""
-
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
"""
-
component = BugTrackerComponent()
component.name = component_name
component.component_group = self
@@ -327,6 +331,7 @@
_filing_url_patterns = {
BugTrackerType.BUGZILLA: (
"%(base_url)s/enter_bug.cgi?product=%(remote_product)s"
+ "&component=%(remote_component)s"
"&short_desc=%(summary)s&long_desc=%(description)s"),
BugTrackerType.GOOGLE_CODE: (
"%(base_url)s/entry?summary=%(summary)s&"
@@ -385,6 +390,7 @@
return {
gnome_bugzilla: (
"%(base_url)s/enter_bug.cgi?product=%(remote_product)s"
+ "&component=%(remote_component)s"
"&short_desc=%(summary)s&comment=%(description)s"),
}
@@ -401,7 +407,8 @@
else:
return False
- def getBugFilingAndSearchLinks(self, remote_product, summary=None,
+ def getBugFilingAndSearchLinks(self, remote_product,
+ remote_component=None, summary=None,
description=None):
"""See `IBugTracker`."""
bugtracker_urls = {'bug_filing_url': None, 'bug_search_url': None}
@@ -416,6 +423,10 @@
# quote() doesn't blow up later on.
remote_product = ''
+ if remote_component is None:
+ # Ditto for remote component.
+ remote_component = ''
+
if self in self._custom_filing_url_patterns:
# Some bugtrackers are customised to accept different
# querystring parameters from the default. We special-case
@@ -473,6 +484,7 @@
url_components = {
'base_url': base_url,
'remote_product': quote(remote_product),
+ 'remote_component': quote(remote_component),
'summary': quote(summary),
'description': quote(description),
}
@@ -673,9 +685,17 @@
"""See `IBugTracker`."""
component_group = None
store = IStore(BugTrackerComponentGroup)
- component_group = store.find(
- BugTrackerComponentGroup,
- name = component_group_name).one()
+ if component_group_name is None:
+ return None
+ elif component_group_name.isdigit():
+ component_group_id = int(component_group_name)
+ component_group = store.find(
+ BugTrackerComponentGroup,
+ id = component_group_id).one()
+ else:
+ component_group = store.find(
+ BugTrackerComponentGroup,
+ name = component_group_name).one()
return component_group
=== added file 'lib/lp/bugs/templates/bugtracker-edit-component.pt'
--- lib/lp/bugs/templates/bugtracker-edit-component.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/templates/bugtracker-edit-component.pt 2010-11-16 19:54:52 +0000
@@ -0,0 +1,16 @@
+<bug-tracker-edit-component
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ metal:use-macro="view/macro:page/main_only"
+ i18n:domain="malone">
+
+ <div metal:fill-slot="main">
+ <h1>Link <span tal:replace="context/component_group/name"/> component '<span tal:replace="context/name"/>'</h1>
+ <div metal:use-macro="context/@@launchpad_form/form">
+ <p>Configure component</p>
+ </div>
+ </div>
+
+</bug-tracker-edit-component>
=== modified file 'lib/lp/bugs/templates/bugtracker-index.pt'
--- lib/lp/bugs/templates/bugtracker-index.pt 2009-09-01 15:58:46 +0000
+++ lib/lp/bugs/templates/bugtracker-index.pt 2010-11-16 19:54:52 +0000
@@ -34,6 +34,9 @@
<div class="yui-g">
<div class="first yui-u">
<div tal:replace="structure context/@@+portlet-details" />
+ <div tal:condition="features/bugtracker_components">
+ <div tal:replace="structure context/@@+portlet-components" />
+ </div>
</div>
<div class="yui-u">
<div tal:replace="structure context/@@+portlet-projects" />
=== 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 2010-11-16 19:54:52 +0000
@@ -0,0 +1,37 @@
+<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">
+ <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>
+ <ul tal:define="related_component_groups view/related_component_groups">
+ <li tal:repeat="component_group related_component_groups">
+ <strong><span tal:replace="component_group/name" /></strong>
+ <ul tal:define="components component_group/components">
+ <li tal:repeat="component components">
+ <span tal:replace="component/name" />
+
+ <span tal:condition="component/distro_source_package">
+ <a class="sprite edit"
+ tal:attributes="href string:${context/fmt:url}/+components/${component_group/id}/${component/id}/+edit">
+ <span tal:replace="structure component/distro_source_package/name"/></a>
+ </span>
+ <a class="sprite add"
+ tal:condition="not: component/distro_source_package"
+ tal:attributes="href string:${context/fmt:url}/+components/${component_group/id}/${component/id}/+edit"></a>
+ </li>
+ <li tal:condition="not: components">
+ <i>This bug tracker has no components for this group</i>
+ </li>
+ </ul>
+ </li>
+ <li tal:condition="not: related_component_groups">
+ <i>This bug tracker has no components</i>
+ </li>
+ </ul>
+</div>
Follow ups