Merge lp:~bryce/launchpad/lp-617695-linkui into lp:launchpad/db-devel

Proposed by Bryce Harrington
Status: Merged
Merge reported by: Curtis Hovey
Merged at revision: not available
Proposed branch: lp:~bryce/launchpad/lp-617695-linkui
Merge into: lp:launchpad/db-devel
Diff against target: 541 lines (+329/-10) (has conflicts)
12 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+3/-0)
lib/canonical/launchpad/scripts/bzremotecomponentfinder.py (+0/-1)
lib/lp/bugs/browser/bugtracker.py (+103/-4)
lib/lp/bugs/browser/configure.zcml (+9/-0)
lib/lp/bugs/browser/tests/test_bugtracker_component.py (+105/-0)
lib/lp/bugs/browser/widgets/bugtask.py (+7/-0)
lib/lp/bugs/configure.zcml (+1/-0)
lib/lp/bugs/interfaces/bugtracker.py (+22/-1)
lib/lp/bugs/model/bugtracker.py (+28/-4)
lib/lp/bugs/templates/bugtracker-index.pt (+3/-0)
lib/lp/bugs/templates/bugtracker-portlet-components.pt (+37/-0)
lib/lp/bugs/tests/test_bugtracker_components.py (+11/-0)
Text conflict in lib/lp/bugs/browser/bugtracker.py
To merge this branch: bzr merge lp:~bryce/launchpad/lp-617695-linkui
Reviewer Review Type Date Requested Status
Steve Kowalik (community) Needs Resubmitting
Curtis Hovey (community) code Needs Information
Tim Penhey (community) Abstain
Bryce Harrington (community) Needs Resubmitting
Review via email: mp+39679@code.launchpad.net

Commit message

Implements UI for displaying components registered at a remote bug tracker, and UI for linking a distro source package to a component.

Description of the change

This branch contains the UI changes to implement display of components and component groups (products) available from a given bug tracker, along with an edit form for linking components to particular source packages.

A preimplementation call was done with Deryck about the design and implementation of the ui and code. He also gave some advice on the tests while we were at UDS.

Lint issues have been checked and fixed.

I test this using the command:

  ./bin/test -t browser.*bugtracker_component

I've also run this through ec2 test. The test run failed due to windmill issues; but, these failures seem unrelated to my branch.

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) wrote :

Hi Bryce,

Thanks for the excellent work! I only have one comment, which is the unittest machinery in the unittest and the import of unittest isn't needed.

review: Approve (code*)
Revision history for this message
Tim Penhey (thumper) wrote :

Why assume ubuntu as the distributions? Do we need to define the distribution at all? Surely the sourcepackagename itself is enough?

Also as a general rule, we should be doing as little as possible in the view code, so the code to link the component to the sourcepackage should really go in the bugtracker code. Warning bells went off when you overloaded updateContextFromData.

review: Needs Information
Revision history for this message
Steve Kowalik (stevenk) wrote :

Bryce is right, SPN isn't enough, since likely the same package exists in Debian, which why I didn't question that bit.

Revision history for this message
Bryce Harrington (bryce) wrote :

SteveK, thanks I'll look into dropping the unittest stuff.

Tim, yeah the SPN is used to manually construct a distribution_source_package object, which requires having a distribution object. Unfortunately since the user is coming in off the bugtracker page there is (afaik) no context to auto-magically discern what distro they're on. The only ideas I had got to be comically hard or would impose silly UI on the user (like a dropdown with all distros to choose from, even though only 1 distro makes any sense to choose).

But yeah, I can move the linking code into the model. I'd considered that but opted to be lazy. ;-)

Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi Bryce.

How do I see this feature? I added (default, 0, bugtracker_components, on) to launchpad_dev and I do not see your portlet added to the bugtracker page.

Revision history for this message
Tim Penhey (thumper) wrote :

The point that I was trying to make is that a component relates to a source package. By that I mean something like "gstreamer". It doesn't really matter that source package is in ubuntu / debian / distroX (to me), if the component relates to the package "gstreamer" then surely that should be enough.

I wasn't challenging the design of our "SourcePackage" object. Only trying to understand what we are fundamentally trying to model.

Revision history for this message
Bryce Harrington (bryce) wrote :

@Curtis, running cronscripts/update-bugzilla-remote-components.py will fill your local database with bugzilla components and give you a good visual of what the UI will look like in practice.

For manual reviewing of the UI elements, after I ran that to download the gnome bugzilla data, I configured alsa-utils as using the gnome bug tracker as its upstream tracker, and linked one of the gnome packages like zenity to verify the linking behavior was right.

Revision history for this message
Bryce Harrington (bryce) wrote :

@Tim, I've promoted the Store code from the browser to a proper interface routine, and generalized it a bit so it'll be useful in and of itself. The remaining code in updateContextFromData() seems harder to abstract away, since it'd make the error messages less meaningful. Note that the code that actually does the linking is already implemented in the model as a setter routine. If you think more of the code could be moved out of browser, I'm open to your recommendations.

Regarding the distro source package complexity, yeah I get what you mean and I agree linking to a 'source package' object directly would be a cleaner design. A source package is going to be linked to the same component regardless of the distribution, and it would be better if we could model it without referencing distros at all. However, the way source packages are implemented in launchpad is kind of wonky. There isn't really a 'source package' database table and storm object that this could be hung off of. There *is* a source_package element in the API, so you would think you could link to that, however:

<deryck> bryceh, no, it's ISourcePackage, which is poorly named. That's the package in a distro series.
<deryck> bryceh, so you're only options are to hang this on either SourcePackage (distro series) or DistributionSourcePackage (distro) and the later seems right.
 bryceh, right
 bryceh, sourcepackagename is just table magic really to enable re-use of names across distros.
 bryceh, but you can't really link to it the same way as the other objects. You could, but it's not really used the same way as the other objects.
...
<deryck> bryceh, so you've done it right. DSP is actually the distro source package, as the name suggests, [while] SP would be better named "distro series source package."

Revision history for this message
Bryce Harrington (bryce) wrote :

Hey, just noticed this branch is still not merged, although there's been no further review comments since my last one. Is there anything still needing done here? Looks like it got stalled out in review...

With me back on the distro team my time's rather short, so if anyone could shepherd this the rest of the way into launchpad it'd be really appreciated.

(I don't really need this UI for my own purposes, I just use the API, but would be nice to have this feature for other folks in general.)

review: Needs Resubmitting
Revision history for this message
Bryce Harrington (bryce) wrote :

Hello? Anyone?

Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi Bryce.

I apologise for not following up on this. I will do the code/UI review in a few hours.

Revision history for this message
Tim Penhey (thumper) wrote :

With me now out of the LP team, I'll leave this to SteveK to do with as he likes.

review: Abstain
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (11.1 KiB)

> === modified file 'lib/canonical/widgets/bugtask.py'
Hi Bryce.

I have some questions about some of the methods and properties. I have some suggestions that I think you can make in less than an hour to improve this branch.

> --- lib/canonical/widgets/bugtask.py 2010-11-08 12:52:43 +0000
> +++ lib/canonical/widgets/bugtask.py 2010-12-03 23:48:54 +0000

I think you need to remerge. This module was moved about 4 months ago. I
expect this to be lp/bugs/browser/widgets/bugtask.py. I see conflicts too.

> @@ -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

I think this could be expressed differently. We are not /assuming/ that
the distro is Ubuntu, it is. To ensure it is this, the whole class would
look like:

class UbuntuSourcePackageNameWidget(BugTaskSourcePackageNameWidget):
    """A widget to select Ubuntu packages."""

    def getDistribution(self):
        """See `BugTaskSourcePackageNameWidget`"""
        return getUtility(ILaunchpadCelebrities).ubuntu

> === modified file 'lib/lp/bugs/browser/bugtracker.py'
> --- lib/lp/bugs/browser/bugtracker.py 2010-11-23 23:22:27 +0000
> +++ lib/lp/bugs/browser/bugtracker.py 2010-12-03 23:48:54 +0000
> ...
> @@ -231,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()

Properties docstring do avoid "return" because they are not intended to be
methods. The call site is not interesting. The docstring could be.

        """All component groups and components."""

> @@ -449,16 +463,111 @@
...
> +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)

This is an odd breadcrum...

review: Needs Information (code)
Revision history for this message
Steve Kowalik (stevenk) wrote :

I think this MP should be re-submitted against devel, I suspect the reasoning for targeting db-devel is long past.

review: Needs Resubmitting
Revision history for this message
Bryce Harrington (bryce) wrote :

>> + @property
>> + def page_title(self):
>> + return smartquote(
>> + u'Link a distribution source package to the %s component'
>> + % self.context.name)
>
> This is an odd breadcrumb/page_title. Labels are meant to be verbose,
> while this attribute is meant to be tearse so that it fits in the breadcrumbs:
>
> Bug trackers > AbiSource bug tracker > Link component

I don't understand this review comment. I modeled the page_title for this off of the BugTrackerEditView classes' page_title() which looks like this:

    @property
    def page_title(self):
        return smartquote(
            u'Change details for the "%s" bug tracker' % self.context.title)

Can you elaborate on how you think it should be worded?

Revision history for this message
Curtis Hovey (sinzui) wrote :

Oh no! I did not see any activity on this. I pulled the branch down and have is all cleaned up. I still cannot see the UI. My branch based on yours is
    lp:~sinzui/launchpad/remote-bugtracker-components-ui-0

You can see my commits are only hours ahead of yours.

Lets talk about this.

Revision history for this message
Bryce Harrington (bryce) wrote :

Regarding viewing the UI:

For manual testing of this feature locally, I first ran cronscripts/update-bugzilla-remote-components.py to populate the mozilla and gnome bugzillas instances in the sample data.

Next, I configured the alsa-utils project as using the gnome bugzilla, at https://bugs.launchpad.dev/alsa-utils/+configure-bugtracker by selecting "In a registered bug tracker" and typing in "gnome-bugs"; for the field "Project ID in bug tracker" I typed in "zenity" just to pick a random existing project.

Navigating to https://bugs.launchpad.dev/bugs/bugtrackers/gnome-bugs I scrolled down to the end and under the 'zenity' component group I clicked the icon next to 'general'. On the linking form I specified 'alsa-utils' as the package. I doublechecked that zenity/general now showed a link to 'alsa-utils'.

I logged out, and verified as anonymous that the edit links don't show up on https://bugs.launchpad.dev/bugs/bugtrackers/gnome-bugs.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
2--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-05-31 12:17:52 +0000
3+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-06-02 23:32:14 +0000
4@@ -624,6 +624,9 @@
5 IBugTracker, 'addRemoteComponentGroup', IBugTrackerComponentGroup)
6 patch_collection_return_type(
7 IBugTracker, 'getAllRemoteComponentGroups', IBugTrackerComponentGroup)
8+patch_entry_return_type(
9+ IBugTracker, 'getRemoteComponentForDistroSourcePackage',
10+ IBugTrackerComponent)
11
12 ## IBugTrackerComponent
13 patch_reference_property(
14
15=== modified file 'lib/canonical/launchpad/scripts/bzremotecomponentfinder.py'
16--- lib/canonical/launchpad/scripts/bzremotecomponentfinder.py 2010-12-20 03:21:03 +0000
17+++ lib/canonical/launchpad/scripts/bzremotecomponentfinder.py 2011-06-02 23:32:14 +0000
18@@ -117,7 +117,6 @@
19 self.static_bugzilla_text = static_bugzilla_text
20
21 def getRemoteProductsAndComponents(self, bugtracker_name=None):
22- """"""
23 lp_bugtrackers = getUtility(IBugTrackerSet)
24 if bugtracker_name is not None:
25 lp_bugtrackers = [
26
27=== modified file 'lib/lp/bugs/browser/bugtracker.py'
28--- lib/lp/bugs/browser/bugtracker.py 2011-05-27 21:12:25 +0000
29+++ lib/lp/bugs/browser/bugtracker.py 2011-06-02 23:32:14 +0000
30@@ -10,6 +10,7 @@
31 'BugTrackerBreadcrumb',
32 'BugTrackerComponentGroupNavigation',
33 'BugTrackerEditView',
34+ 'BugTrackerEditComponentView',
35 'BugTrackerNavigation',
36 'BugTrackerNavigationMenu',
37 'BugTrackerSetBreadcrumb',
38@@ -56,6 +57,16 @@
39 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
40 from canonical.launchpad.webapp.menu import NavigationMenu
41 from canonical.lazr.utils import smartquote
42+<<<<<<< TREE
43+=======
44+from canonical.widgets import (
45+ DelimitedListWidget,
46+ LaunchpadRadioWidget,
47+ )
48+from canonical.widgets.bugtask import (
49+ UbuntuSourcePackageNameWidget,
50+ )
51+>>>>>>> MERGE-SOURCE
52 from lp.app.browser.launchpadform import (
53 action,
54 custom_widget,
55@@ -71,7 +82,13 @@
56 IBugTrackerComponentGroup,
57 IBugTrackerSet,
58 IRemoteBug,
59+<<<<<<< TREE
60+=======
61+ IBugTrackerComponent,
62+ IBugTrackerComponentGroup,
63+>>>>>>> MERGE-SOURCE
64 )
65+from lp.registry.interfaces.distribution import IDistributionSet
66 from lp.services.propertycache import cachedproperty
67
68 # A set of bug tracker types for which there can only ever be one bug
69@@ -227,6 +244,11 @@
70 return shortlist(chain(self.context.projects,
71 self.context.products), 100)
72
73+ @property
74+ def related_component_groups(self):
75+ """All component groups and components."""
76+ return self.context.getAllRemoteComponentGroups()
77+
78
79 BUG_TRACKER_ACTIVE_VOCABULARY = SimpleVocabulary.fromItems(
80 [('On', True), ('Off', False)])
81@@ -445,16 +467,93 @@
82 return RemoteBug(self.context, remotebug, bugs)
83
84 @stepthrough("+components")
85- def component_groups(self, name):
86- return self.context.getRemoteComponentGroup(name)
87+ def component_groups(self, id):
88+ # Navigate by id (component group name should work too)
89+ return self.context.getRemoteComponentGroup(id)
90+
91+
92+class BugTrackerEditComponentView(LaunchpadEditFormView):
93+ """Provides editing form for setting source packages for components.
94+
95+ In this class we assume that bug tracker components are always
96+ linked to source packages in the Ubuntu distribution.
97+ """
98+
99+ schema = IBugTrackerComponent
100+ custom_widget('sourcepackagename',
101+ UbuntuSourcePackageNameWidget)
102+
103+ @property
104+ def page_title(self):
105+ return smartquote(
106+ u'Link a distribution source package to the %s component'
107+ % self.context.name)
108+
109+ @property
110+ def field_names(self):
111+ field_names = ['sourcepackagename']
112+ return field_names
113+
114+ @property
115+ def initial_values(self):
116+ """See `LaunchpadFormView.`"""
117+ field_values = {}
118+ for name in self.field_names:
119+ if name == 'sourcepackagename':
120+ pkg = self.context.distro_source_package
121+ if pkg is not None:
122+ field_values['sourcepackagename'] = pkg.name
123+ else:
124+ field_values['sourcepackagename'] = ""
125+ else:
126+ field_values[name] = getattr(self.context, name)
127+
128+ return field_values
129+
130+ def updateContextFromData(self, data, context=None):
131+ """Link component to specified distro source package.
132+
133+ Get the user-provided source package name from the form widget,
134+ look it up in Ubuntu to retrieve the distro_source_package
135+ object, and link it to this component.
136+ """
137+ component = context or self.context
138+ sourcepackagename = data['sourcepackagename']
139+ distribution = self.widgets['sourcepackagename'].getDistribution()
140+ dsp = distribution.getSourcePackage(sourcepackagename)
141+ bug_tracker = self.context.component_group.bug_tracker
142+
143+ # Has this source package already been assigned to a component?
144+ link_comp = bug_tracker.getRemoteComponentForDistroSourcePackageName(
145+ distribution, sourcepackagename)
146+ if link_comp is not None:
147+ self.request.response.addNotification(
148+ "The %s source package is already linked to %s:%s in %s" %(
149+ sourcepackagename.name, link_comp.component_group.name,
150+ link_comp.name, distribution.name))
151+ return
152+
153+ component.distro_source_package = dsp
154+ self.request.response.addNotification(
155+ "%s:%s is now linked to the %s source package in %s" %(
156+ component.component_group.name, component.name,
157+ sourcepackagename.name, distribution.name))
158+
159+ @action('Save Changes', name='save')
160+ def save_action(self, action, data):
161+ """Update the component with the form data."""
162+ self.updateContextFromData(data)
163+
164+ self.next_url = canonical_url(
165+ self.context.component_group.bug_tracker)
166
167
168 class BugTrackerComponentGroupNavigation(Navigation):
169
170 usedfor = IBugTrackerComponentGroup
171
172- def traverse(self, name):
173- return self.context.getComponent(name)
174+ def traverse(self, id):
175+ return self.context.getComponent(id)
176
177
178 class BugTrackerSetBreadcrumb(Breadcrumb):
179
180=== modified file 'lib/lp/bugs/browser/configure.zcml'
181--- lib/lp/bugs/browser/configure.zcml 2011-05-29 21:18:09 +0000
182+++ lib/lp/bugs/browser/configure.zcml 2011-06-02 23:32:14 +0000
183@@ -819,6 +819,12 @@
184 path_expression="name"
185 attribute_to_parent="component_group"
186 rootsite="bugs"/>
187+ <browser:page
188+ name="+edit"
189+ for="lp.bugs.interfaces.bugtracker.IBugTrackerComponent"
190+ class="lp.bugs.browser.bugtracker.BugTrackerEditComponentView"
191+ permission="launchpad.AnyPerson"
192+ template="../../app/templates/generic-edit.pt"/>
193 <browser:pages
194 for="lp.bugs.interfaces.bugtracker.IBugTracker"
195 class="lp.bugs.browser.bugtracker.BugTrackerView"
196@@ -830,6 +836,9 @@
197 name="+portlet-details"
198 template="../templates/bugtracker-portlet-details.pt"/>
199 <browser:page
200+ name="+portlet-components"
201+ template="../templates/bugtracker-portlet-components.pt"/>
202+ <browser:page
203 name="+portlet-projects"
204 template="../templates/bugtracker-portlet-projects.pt"/>
205 <browser:page
206
207=== added file 'lib/lp/bugs/browser/tests/test_bugtracker_component.py'
208--- lib/lp/bugs/browser/tests/test_bugtracker_component.py 1970-01-01 00:00:00 +0000
209+++ lib/lp/bugs/browser/tests/test_bugtracker_component.py 2011-06-02 23:32:14 +0000
210@@ -0,0 +1,105 @@
211+# Copyright 2010 Canonical Ltd. This software is licensed under the
212+# GNU Affero General Public License version (see the file LICENSE).
213+
214+"""Unit tests for linking bug tracker components to source packages."""
215+
216+__metaclass__ = type
217+
218+from zope.component import getUtility
219+
220+from canonical.testing.layers import DatabaseFunctionalLayer
221+from lp.registry.interfaces.distribution import IDistributionSet
222+from lp.testing import (
223+ login_person,
224+ TestCaseWithFactory,
225+ )
226+from lp.testing.views import create_initialized_view
227+
228+
229+class TestBugTrackerEditComponentView(TestCaseWithFactory):
230+
231+ layer = DatabaseFunctionalLayer
232+
233+ def setUp(self):
234+ super(TestBugTrackerEditComponentView, self).setUp()
235+ regular_user = self.factory.makePerson()
236+ login_person(regular_user)
237+
238+ self.bug_tracker = self.factory.makeBugTracker()
239+ self.comp_group = self.factory.makeBugTrackerComponentGroup(
240+ u'alpha', self.bug_tracker)
241+
242+ def _makeForm(self, sourcepackage):
243+ if sourcepackage is None:
244+ name = ''
245+ else:
246+ name = sourcepackage.name
247+ return {
248+ 'field.sourcepackagename': name,
249+ 'field.actions.save': 'Save',
250+ }
251+
252+ def test_view_attributes(self):
253+ component = self.factory.makeBugTrackerComponent(
254+ u'Example', self.comp_group)
255+ distro = getUtility(IDistributionSet).getByName('ubuntu')
256+ package = self.factory.makeDistributionSourcePackage(
257+ sourcepackagename='example', distribution=distro)
258+ form = self._makeForm(package)
259+ view = create_initialized_view(
260+ component, name='+edit', form=form)
261+ label = 'Link a distribution source package to the Example component'
262+ self.assertEqual(label, view.page_title)
263+ fields = ['sourcepackagename']
264+ self.assertEqual(fields, view.field_names)
265+
266+ def test_linking(self):
267+ component = self.factory.makeBugTrackerComponent(
268+ u'Example', self.comp_group)
269+ distro = getUtility(IDistributionSet).getByName('ubuntu')
270+ package = self.factory.makeDistributionSourcePackage(
271+ sourcepackagename='example', distribution=distro)
272+
273+ self.assertIs(None, component.distro_source_package)
274+ form = self._makeForm(package)
275+ view = create_initialized_view(
276+ component, name='+edit', form=form)
277+ self.assertEqual([], view.errors)
278+
279+ notifications = view.request.response.notifications
280+ #self.assertEqual(1, len(notifications))
281+ self.assertEqual(component.distro_source_package, package)
282+ expected = (
283+ u"alpha:Example is now linked to the example "
284+ "source package in ubuntu")
285+ self.assertEqual(expected, notifications.pop().message)
286+
287+ def test_cannot_doublelink_sourcepackages(self):
288+ # Two components try linking to same package
289+ component_a = self.factory.makeBugTrackerComponent(
290+ u'a', self.comp_group)
291+ component_b = self.factory.makeBugTrackerComponent(
292+ u'b', self.comp_group)
293+ distro = getUtility(IDistributionSet).getByName('ubuntu')
294+ package = self.factory.makeDistributionSourcePackage(
295+ sourcepackagename='example', distribution=distro)
296+
297+ form = self._makeForm(package)
298+ view = create_initialized_view(
299+ component_a, name='+edit', form=form)
300+ notifications = view.request.response.notifications
301+ self.assertEqual([], view.errors)
302+ self.assertEqual(1, len(notifications))
303+ self.assertEqual(package, component_a.distro_source_package)
304+
305+ form = self._makeForm(package)
306+ view = create_initialized_view(
307+ component_b, name='+edit', form=form)
308+ self.assertIs(None, component_b.distro_source_package)
309+ self.assertEqual([], view.errors)
310+ notifications = view.request.response.notifications
311+ self.assertEqual(1, len(notifications))
312+ expected = (
313+ u"""The example source package is already linked to """
314+ """alpha:a in ubuntu""")
315+ self.assertEqual(expected, notifications.pop().message)
316
317=== modified file 'lib/lp/bugs/browser/widgets/bugtask.py'
318--- lib/lp/bugs/browser/widgets/bugtask.py 2011-02-02 15:43:31 +0000
319+++ lib/lp/bugs/browser/widgets/bugtask.py 2011-06-02 23:32:14 +0000
320@@ -527,6 +527,13 @@
321 return distribution
322
323
324+class UbuntuSourcePackageNameWidget(BugTaskSourcePackageNameWidget):
325+ """A widget to select Ubuntu packages."""
326+ def getDistribution(self):
327+ """See `BugTaskSourcePackageNameWidget`"""
328+ return getUtility(ILaunchpadCelebrities).ubuntu
329+
330+
331 class AssigneeDisplayWidget(BrowserWidget):
332 """A widget for displaying an assignee."""
333
334
335=== modified file 'lib/lp/bugs/configure.zcml'
336--- lib/lp/bugs/configure.zcml 2011-05-10 15:14:10 +0000
337+++ lib/lp/bugs/configure.zcml 2011-06-02 23:32:14 +0000
338@@ -394,6 +394,7 @@
339 getBugFilingAndSearchLinks
340 getBugsWatching
341 getLinkedPersonByName
342+ getRemoteComponentForDistroSourcePackageName
343 getRemoteComponentGroup
344 has_lp_plugin
345 id
346
347=== modified file 'lib/lp/bugs/interfaces/bugtracker.py'
348--- lib/lp/bugs/interfaces/bugtracker.py 2011-02-23 20:26:53 +0000
349+++ lib/lp/bugs/interfaces/bugtracker.py 2011-06-02 23:32:14 +0000
350@@ -390,7 +390,8 @@
351
352 @operation_parameters(
353 component_group_name=TextLine(
354- title=u"The name of the remote component group", required=True))
355+ title=u"The name of the remote component group",
356+ required=True))
357 @operation_returns_entry(Interface)
358 @export_read_operation()
359 def getRemoteComponentGroup(component_group_name):
360@@ -399,6 +400,22 @@
361 :param component_group_name: Name of the component group to retrieve.
362 """
363
364+ @operation_parameters(
365+ distro_name=TextLine(
366+ title=u"The name of the distribution for the source package",
367+ required=True),
368+ source_package_name=TextLine(
369+ title=u"The name of the source package to look for",
370+ required=True))
371+ @operation_returns_entry(Interface)
372+ @export_read_operation()
373+ def getRemoteComponentForDistroSourcePackage(
374+ distro_name, source_package_name):
375+ """Returns the component linked to this source package, if any.
376+
377+ If no components have been linked, returns value of None.
378+ """
379+
380
381 class IBugTrackerSet(Interface):
382 """A set of IBugTracker's.
383@@ -538,6 +555,10 @@
384 title=_('Name'),
385 description=_("The name of a software component "
386 "as shown in Launchpad.")))
387+ sourcepackagename = Choice(
388+ title=_("Package"), required=False, vocabulary='SourcePackageName')
389+ distribution = Choice(
390+ title=_("Distribution"), required=False, vocabulary='Distribution')
391
392 distro_source_package = exported(
393 Reference(
394
395=== modified file 'lib/lp/bugs/model/bugtracker.py'
396--- lib/lp/bugs/model/bugtracker.py 2011-05-28 04:09:11 +0000
397+++ lib/lp/bugs/model/bugtracker.py 2011-06-02 23:32:14 +0000
398@@ -83,6 +83,7 @@
399 from lp.bugs.model.bugmessage import BugMessage
400 from lp.bugs.model.bugtrackerperson import BugTrackerPerson
401 from lp.bugs.model.bugwatch import BugWatch
402+from lp.registry.interfaces.distribution import IDistributionSet
403 from lp.registry.interfaces.person import (
404 IPersonSet,
405 validate_public_person,
406@@ -271,10 +272,17 @@
407
408 if component_name is None:
409 return None
410+ elif component_name.isdigit():
411+ component_id = int(component_name)
412+ return Store.of(self).find(
413+ BugTrackerComponent,
414+ BugTrackerComponent.id == component_id,
415+ BugTrackerComponent.component_group == self.id).one()
416 else:
417 return Store.of(self).find(
418 BugTrackerComponent,
419- (BugTrackerComponent.name == component_name)).one()
420+ BugTrackerComponent.name == component_name,
421+ BugTrackerComponent.component_group == self.id).one()
422
423 def addCustomComponent(self, component_name):
424 """Adds a component locally that isn't synced from a remote tracker
425@@ -680,11 +688,27 @@
426 """See `IBugTracker`."""
427 component_group = None
428 store = IStore(BugTrackerComponentGroup)
429- component_group = store.find(
430- BugTrackerComponentGroup,
431- name = component_group_name).one()
432+ if component_group_name.isdigit():
433+ component_group_id = int(component_group_name)
434+ component_group = store.find(
435+ BugTrackerComponentGroup,
436+ id = component_group_id).one()
437+ else:
438+ component_group = store.find(
439+ BugTrackerComponentGroup,
440+ name = component_group_name).one()
441 return component_group
442
443+ def getRemoteComponentForDistroSourcePackageName(
444+ self, distribution, sourcepackagename):
445+ """See `IBugTracker`."""
446+ pkgs = Store.of(self).find(
447+ BugTrackerComponent,
448+ BugTrackerComponent.distribution == distribution.id,
449+ BugTrackerComponent.source_package_name ==
450+ sourcepackagename.id).one()
451+ return pkgs[0]
452+
453
454 class BugTrackerSet:
455 """Implements IBugTrackerSet for a container or set of BugTrackers,
456
457=== modified file 'lib/lp/bugs/templates/bugtracker-index.pt'
458--- lib/lp/bugs/templates/bugtracker-index.pt 2010-10-10 21:54:16 +0000
459+++ lib/lp/bugs/templates/bugtracker-index.pt 2011-06-02 23:32:14 +0000
460@@ -34,6 +34,9 @@
461 <div class="yui-g">
462 <div class="first yui-u">
463 <div tal:replace="structure context/@@+portlet-details" />
464+ <div tal:condition="features/bugtracker_components">
465+ <div tal:replace="structure context/@@+portlet-components" />
466+ </div>
467 </div>
468 <div class="yui-u">
469 <div tal:replace="structure context/@@+portlet-projects" />
470
471=== added file 'lib/lp/bugs/templates/bugtracker-portlet-components.pt'
472--- lib/lp/bugs/templates/bugtracker-portlet-components.pt 1970-01-01 00:00:00 +0000
473+++ lib/lp/bugs/templates/bugtracker-portlet-components.pt 2011-06-02 23:32:14 +0000
474@@ -0,0 +1,37 @@
475+<div
476+ xmlns:tal="http://xml.zope.org/namespaces/tal"
477+ xmlns:metal="http://xml.zope.org/namespaces/metal"
478+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
479+ class="portlet" id="portlet-components">
480+ <h2>Components</h2>
481+ <p>
482+ You can link components from this bug tracker to their corresponding
483+ distribution source packages in the project's &ldquo;Change
484+ components&rdquo; page.
485+ </p>
486+ <ul tal:define="related_component_groups view/related_component_groups">
487+ <li tal:repeat="component_group related_component_groups">
488+ <strong><span tal:replace="component_group/name" /></strong>
489+ <ul tal:define="components component_group/components">
490+ <li tal:repeat="component components">
491+ <span tal:replace="component/name" />
492+ &nbsp;
493+ <span tal:condition="component/distro_source_package">
494+ <a class="sprite edit"
495+ tal:attributes="href string:${context/fmt:url}/+components/${component_group/id}/${component/id}/+edit">
496+ <span tal:replace="structure component/distro_source_package/name"/></a>
497+ </span>
498+ <a class="sprite add"
499+ tal:condition="not: component/distro_source_package"
500+ tal:attributes="href string:${context/fmt:url}/+components/${component_group/id}/${component/id}/+edit"></a>
501+ </li>
502+ <li tal:condition="not: components">
503+ <i>This bug tracker has no components for this group</i>
504+ </li>
505+ </ul>
506+ </li>
507+ <li tal:condition="not: related_component_groups">
508+ <i>This bug tracker has no components</i>
509+ </li>
510+ </ul>
511+</div>
512
513=== modified file 'lib/lp/bugs/tests/test_bugtracker_components.py'
514--- lib/lp/bugs/tests/test_bugtracker_components.py 2010-10-15 06:01:53 +0000
515+++ lib/lp/bugs/tests/test_bugtracker_components.py 2011-06-02 23:32:14 +0000
516@@ -88,15 +88,26 @@
517
518 def test_link_distro_source_package(self):
519 """Check that a link can be set to a distro source package"""
520+ ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
521 component = self.factory.makeBugTrackerComponent(
522 u'example', self.comp_group)
523 package = self.factory.makeDistributionSourcePackage()
524 self.assertIs(None, component.distro_source_package)
525
526+ # No components link to the source package yet
527+ link_comp = self.bug_tracker.getRemoteComponentForDistroSourcePackageName(
528+ ubuntu, component.distro_source_package.name)
529+ self.assertIs(None, link_comp)
530+
531 # Set the source package on the component
532 component.distro_source_package = package
533 self.assertIsNot(None, component.distro_source_package)
534
535+ # Verify we can find the component by the source package now
536+ link_comp = self.bug_tracker.getRemoteComponentForDistroSourcePackageName(
537+ ubuntu, component.distro_source_package.name)
538+ self.assertIsNot(None, link_comp)
539+
540
541 class TestBugTrackerWithComponents(TestCaseWithFactory):
542

Subscribers

People subscribed via source and target branches

to status/vote changes: