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
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-05-31 12:17:52 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-06-02 23:32:14 +0000
@@ -624,6 +624,9 @@
624 IBugTracker, 'addRemoteComponentGroup', IBugTrackerComponentGroup)624 IBugTracker, 'addRemoteComponentGroup', IBugTrackerComponentGroup)
625patch_collection_return_type(625patch_collection_return_type(
626 IBugTracker, 'getAllRemoteComponentGroups', IBugTrackerComponentGroup)626 IBugTracker, 'getAllRemoteComponentGroups', IBugTrackerComponentGroup)
627patch_entry_return_type(
628 IBugTracker, 'getRemoteComponentForDistroSourcePackage',
629 IBugTrackerComponent)
627630
628## IBugTrackerComponent631## IBugTrackerComponent
629patch_reference_property(632patch_reference_property(
630633
=== modified file 'lib/canonical/launchpad/scripts/bzremotecomponentfinder.py'
--- lib/canonical/launchpad/scripts/bzremotecomponentfinder.py 2010-12-20 03:21:03 +0000
+++ lib/canonical/launchpad/scripts/bzremotecomponentfinder.py 2011-06-02 23:32:14 +0000
@@ -117,7 +117,6 @@
117 self.static_bugzilla_text = static_bugzilla_text117 self.static_bugzilla_text = static_bugzilla_text
118118
119 def getRemoteProductsAndComponents(self, bugtracker_name=None):119 def getRemoteProductsAndComponents(self, bugtracker_name=None):
120 """"""
121 lp_bugtrackers = getUtility(IBugTrackerSet)120 lp_bugtrackers = getUtility(IBugTrackerSet)
122 if bugtracker_name is not None:121 if bugtracker_name is not None:
123 lp_bugtrackers = [122 lp_bugtrackers = [
124123
=== 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-02 23:32:14 +0000
@@ -10,6 +10,7 @@
10 'BugTrackerBreadcrumb',10 'BugTrackerBreadcrumb',
11 'BugTrackerComponentGroupNavigation',11 'BugTrackerComponentGroupNavigation',
12 'BugTrackerEditView',12 'BugTrackerEditView',
13 'BugTrackerEditComponentView',
13 'BugTrackerNavigation',14 'BugTrackerNavigation',
14 'BugTrackerNavigationMenu',15 'BugTrackerNavigationMenu',
15 'BugTrackerSetBreadcrumb',16 'BugTrackerSetBreadcrumb',
@@ -56,6 +57,16 @@
56from canonical.launchpad.webapp.breadcrumb import Breadcrumb57from canonical.launchpad.webapp.breadcrumb import Breadcrumb
57from canonical.launchpad.webapp.menu import NavigationMenu58from canonical.launchpad.webapp.menu import NavigationMenu
58from canonical.lazr.utils import smartquote59from canonical.lazr.utils import smartquote
60<<<<<<< TREE
61=======
62from canonical.widgets import (
63 DelimitedListWidget,
64 LaunchpadRadioWidget,
65 )
66from canonical.widgets.bugtask import (
67 UbuntuSourcePackageNameWidget,
68 )
69>>>>>>> MERGE-SOURCE
59from lp.app.browser.launchpadform import (70from lp.app.browser.launchpadform import (
60 action,71 action,
61 custom_widget,72 custom_widget,
@@ -71,7 +82,13 @@
71 IBugTrackerComponentGroup,82 IBugTrackerComponentGroup,
72 IBugTrackerSet,83 IBugTrackerSet,
73 IRemoteBug,84 IRemoteBug,
85<<<<<<< TREE
86=======
87 IBugTrackerComponent,
88 IBugTrackerComponentGroup,
89>>>>>>> MERGE-SOURCE
74 )90 )
91from lp.registry.interfaces.distribution import IDistributionSet
75from lp.services.propertycache import cachedproperty92from lp.services.propertycache import cachedproperty
7693
77# A set of bug tracker types for which there can only ever be one bug94# A set of bug tracker types for which there can only ever be one bug
@@ -227,6 +244,11 @@
227 return shortlist(chain(self.context.projects,244 return shortlist(chain(self.context.projects,
228 self.context.products), 100)245 self.context.products), 100)
229246
247 @property
248 def related_component_groups(self):
249 """All component groups and components."""
250 return self.context.getAllRemoteComponentGroups()
251
230252
231BUG_TRACKER_ACTIVE_VOCABULARY = SimpleVocabulary.fromItems(253BUG_TRACKER_ACTIVE_VOCABULARY = SimpleVocabulary.fromItems(
232 [('On', True), ('Off', False)])254 [('On', True), ('Off', False)])
@@ -445,16 +467,93 @@
445 return RemoteBug(self.context, remotebug, bugs)467 return RemoteBug(self.context, remotebug, bugs)
446468
447 @stepthrough("+components")469 @stepthrough("+components")
448 def component_groups(self, name):470 def component_groups(self, id):
449 return self.context.getRemoteComponentGroup(name)471 # Navigate by id (component group name should work too)
472 return self.context.getRemoteComponentGroup(id)
473
474
475class BugTrackerEditComponentView(LaunchpadEditFormView):
476 """Provides editing form for setting source packages for components.
477
478 In this class we assume that bug tracker components are always
479 linked to source packages in the Ubuntu distribution.
480 """
481
482 schema = IBugTrackerComponent
483 custom_widget('sourcepackagename',
484 UbuntuSourcePackageNameWidget)
485
486 @property
487 def page_title(self):
488 return smartquote(
489 u'Link a distribution source package to the %s component'
490 % self.context.name)
491
492 @property
493 def field_names(self):
494 field_names = ['sourcepackagename']
495 return field_names
496
497 @property
498 def initial_values(self):
499 """See `LaunchpadFormView.`"""
500 field_values = {}
501 for name in self.field_names:
502 if name == 'sourcepackagename':
503 pkg = self.context.distro_source_package
504 if pkg is not None:
505 field_values['sourcepackagename'] = pkg.name
506 else:
507 field_values['sourcepackagename'] = ""
508 else:
509 field_values[name] = getattr(self.context, name)
510
511 return field_values
512
513 def updateContextFromData(self, data, context=None):
514 """Link component to specified distro source package.
515
516 Get the user-provided source package name from the form widget,
517 look it up in Ubuntu to retrieve the distro_source_package
518 object, and link it to this component.
519 """
520 component = context or self.context
521 sourcepackagename = data['sourcepackagename']
522 distribution = self.widgets['sourcepackagename'].getDistribution()
523 dsp = distribution.getSourcePackage(sourcepackagename)
524 bug_tracker = self.context.component_group.bug_tracker
525
526 # Has this source package already been assigned to a component?
527 link_comp = bug_tracker.getRemoteComponentForDistroSourcePackageName(
528 distribution, sourcepackagename)
529 if link_comp is not None:
530 self.request.response.addNotification(
531 "The %s source package is already linked to %s:%s in %s" %(
532 sourcepackagename.name, link_comp.component_group.name,
533 link_comp.name, distribution.name))
534 return
535
536 component.distro_source_package = dsp
537 self.request.response.addNotification(
538 "%s:%s is now linked to the %s source package in %s" %(
539 component.component_group.name, component.name,
540 sourcepackagename.name, distribution.name))
541
542 @action('Save Changes', name='save')
543 def save_action(self, action, data):
544 """Update the component with the form data."""
545 self.updateContextFromData(data)
546
547 self.next_url = canonical_url(
548 self.context.component_group.bug_tracker)
450549
451550
452class BugTrackerComponentGroupNavigation(Navigation):551class BugTrackerComponentGroupNavigation(Navigation):
453552
454 usedfor = IBugTrackerComponentGroup553 usedfor = IBugTrackerComponentGroup
455554
456 def traverse(self, name):555 def traverse(self, id):
457 return self.context.getComponent(name)556 return self.context.getComponent(id)
458557
459558
460class BugTrackerSetBreadcrumb(Breadcrumb):559class BugTrackerSetBreadcrumb(Breadcrumb):
461560
=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml 2011-05-29 21:18:09 +0000
+++ lib/lp/bugs/browser/configure.zcml 2011-06-02 23:32:14 +0000
@@ -819,6 +819,12 @@
819 path_expression="name"819 path_expression="name"
820 attribute_to_parent="component_group"820 attribute_to_parent="component_group"
821 rootsite="bugs"/>821 rootsite="bugs"/>
822 <browser:page
823 name="+edit"
824 for="lp.bugs.interfaces.bugtracker.IBugTrackerComponent"
825 class="lp.bugs.browser.bugtracker.BugTrackerEditComponentView"
826 permission="launchpad.AnyPerson"
827 template="../../app/templates/generic-edit.pt"/>
822 <browser:pages828 <browser:pages
823 for="lp.bugs.interfaces.bugtracker.IBugTracker"829 for="lp.bugs.interfaces.bugtracker.IBugTracker"
824 class="lp.bugs.browser.bugtracker.BugTrackerView"830 class="lp.bugs.browser.bugtracker.BugTrackerView"
@@ -830,6 +836,9 @@
830 name="+portlet-details"836 name="+portlet-details"
831 template="../templates/bugtracker-portlet-details.pt"/>837 template="../templates/bugtracker-portlet-details.pt"/>
832 <browser:page838 <browser:page
839 name="+portlet-components"
840 template="../templates/bugtracker-portlet-components.pt"/>
841 <browser:page
833 name="+portlet-projects"842 name="+portlet-projects"
834 template="../templates/bugtracker-portlet-projects.pt"/>843 template="../templates/bugtracker-portlet-projects.pt"/>
835 <browser:page844 <browser:page
836845
=== 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-02 23:32:14 +0000
@@ -0,0 +1,105 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version (see the file LICENSE).
3
4"""Unit tests for linking bug tracker components to source packages."""
5
6__metaclass__ = type
7
8from zope.component import getUtility
9
10from canonical.testing.layers import DatabaseFunctionalLayer
11from lp.registry.interfaces.distribution import IDistributionSet
12from lp.testing import (
13 login_person,
14 TestCaseWithFactory,
15 )
16from lp.testing.views import create_initialized_view
17
18
19class TestBugTrackerEditComponentView(TestCaseWithFactory):
20
21 layer = DatabaseFunctionalLayer
22
23 def setUp(self):
24 super(TestBugTrackerEditComponentView, self).setUp()
25 regular_user = self.factory.makePerson()
26 login_person(regular_user)
27
28 self.bug_tracker = self.factory.makeBugTracker()
29 self.comp_group = self.factory.makeBugTrackerComponentGroup(
30 u'alpha', self.bug_tracker)
31
32 def _makeForm(self, sourcepackage):
33 if sourcepackage is None:
34 name = ''
35 else:
36 name = sourcepackage.name
37 return {
38 'field.sourcepackagename': name,
39 'field.actions.save': 'Save',
40 }
41
42 def test_view_attributes(self):
43 component = self.factory.makeBugTrackerComponent(
44 u'Example', self.comp_group)
45 distro = getUtility(IDistributionSet).getByName('ubuntu')
46 package = self.factory.makeDistributionSourcePackage(
47 sourcepackagename='example', distribution=distro)
48 form = self._makeForm(package)
49 view = create_initialized_view(
50 component, name='+edit', form=form)
51 label = 'Link a distribution source package to the Example component'
52 self.assertEqual(label, view.page_title)
53 fields = ['sourcepackagename']
54 self.assertEqual(fields, view.field_names)
55
56 def test_linking(self):
57 component = self.factory.makeBugTrackerComponent(
58 u'Example', self.comp_group)
59 distro = getUtility(IDistributionSet).getByName('ubuntu')
60 package = self.factory.makeDistributionSourcePackage(
61 sourcepackagename='example', distribution=distro)
62
63 self.assertIs(None, component.distro_source_package)
64 form = self._makeForm(package)
65 view = create_initialized_view(
66 component, name='+edit', form=form)
67 self.assertEqual([], view.errors)
68
69 notifications = view.request.response.notifications
70 #self.assertEqual(1, len(notifications))
71 self.assertEqual(component.distro_source_package, package)
72 expected = (
73 u"alpha:Example is now linked to the example "
74 "source package in ubuntu")
75 self.assertEqual(expected, notifications.pop().message)
76
77 def test_cannot_doublelink_sourcepackages(self):
78 # Two components try linking to same package
79 component_a = self.factory.makeBugTrackerComponent(
80 u'a', self.comp_group)
81 component_b = self.factory.makeBugTrackerComponent(
82 u'b', self.comp_group)
83 distro = getUtility(IDistributionSet).getByName('ubuntu')
84 package = self.factory.makeDistributionSourcePackage(
85 sourcepackagename='example', distribution=distro)
86
87 form = self._makeForm(package)
88 view = create_initialized_view(
89 component_a, name='+edit', form=form)
90 notifications = view.request.response.notifications
91 self.assertEqual([], view.errors)
92 self.assertEqual(1, len(notifications))
93 self.assertEqual(package, component_a.distro_source_package)
94
95 form = self._makeForm(package)
96 view = create_initialized_view(
97 component_b, name='+edit', form=form)
98 self.assertIs(None, component_b.distro_source_package)
99 self.assertEqual([], view.errors)
100 notifications = view.request.response.notifications
101 self.assertEqual(1, len(notifications))
102 expected = (
103 u"""The example source package is already linked to """
104 """alpha:a in ubuntu""")
105 self.assertEqual(expected, notifications.pop().message)
0106
=== 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-02 23:32:14 +0000
@@ -527,6 +527,13 @@
527 return distribution527 return distribution
528528
529529
530class UbuntuSourcePackageNameWidget(BugTaskSourcePackageNameWidget):
531 """A widget to select Ubuntu packages."""
532 def getDistribution(self):
533 """See `BugTaskSourcePackageNameWidget`"""
534 return getUtility(ILaunchpadCelebrities).ubuntu
535
536
530class AssigneeDisplayWidget(BrowserWidget):537class AssigneeDisplayWidget(BrowserWidget):
531 """A widget for displaying an assignee."""538 """A widget for displaying an assignee."""
532539
533540
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2011-05-10 15:14:10 +0000
+++ lib/lp/bugs/configure.zcml 2011-06-02 23:32:14 +0000
@@ -394,6 +394,7 @@
394 getBugFilingAndSearchLinks394 getBugFilingAndSearchLinks
395 getBugsWatching395 getBugsWatching
396 getLinkedPersonByName396 getLinkedPersonByName
397 getRemoteComponentForDistroSourcePackageName
397 getRemoteComponentGroup398 getRemoteComponentGroup
398 has_lp_plugin399 has_lp_plugin
399 id400 id
400401
=== 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-02 23:32:14 +0000
@@ -390,7 +390,8 @@
390390
391 @operation_parameters(391 @operation_parameters(
392 component_group_name=TextLine(392 component_group_name=TextLine(
393 title=u"The name of the remote component group", required=True))393 title=u"The name of the remote component group",
394 required=True))
394 @operation_returns_entry(Interface)395 @operation_returns_entry(Interface)
395 @export_read_operation()396 @export_read_operation()
396 def getRemoteComponentGroup(component_group_name):397 def getRemoteComponentGroup(component_group_name):
@@ -399,6 +400,22 @@
399 :param component_group_name: Name of the component group to retrieve.400 :param component_group_name: Name of the component group to retrieve.
400 """401 """
401402
403 @operation_parameters(
404 distro_name=TextLine(
405 title=u"The name of the distribution for the source package",
406 required=True),
407 source_package_name=TextLine(
408 title=u"The name of the source package to look for",
409 required=True))
410 @operation_returns_entry(Interface)
411 @export_read_operation()
412 def getRemoteComponentForDistroSourcePackage(
413 distro_name, source_package_name):
414 """Returns the component linked to this source package, if any.
415
416 If no components have been linked, returns value of None.
417 """
418
402419
403class IBugTrackerSet(Interface):420class IBugTrackerSet(Interface):
404 """A set of IBugTracker's.421 """A set of IBugTracker's.
@@ -538,6 +555,10 @@
538 title=_('Name'),555 title=_('Name'),
539 description=_("The name of a software component "556 description=_("The name of a software component "
540 "as shown in Launchpad.")))557 "as shown in Launchpad.")))
558 sourcepackagename = Choice(
559 title=_("Package"), required=False, vocabulary='SourcePackageName')
560 distribution = Choice(
561 title=_("Distribution"), required=False, vocabulary='Distribution')
541562
542 distro_source_package = exported(563 distro_source_package = exported(
543 Reference(564 Reference(
544565
=== 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-02 23:32:14 +0000
@@ -83,6 +83,7 @@
83from lp.bugs.model.bugmessage import BugMessage83from lp.bugs.model.bugmessage import BugMessage
84from lp.bugs.model.bugtrackerperson import BugTrackerPerson84from lp.bugs.model.bugtrackerperson import BugTrackerPerson
85from lp.bugs.model.bugwatch import BugWatch85from lp.bugs.model.bugwatch import BugWatch
86from lp.registry.interfaces.distribution import IDistributionSet
86from lp.registry.interfaces.person import (87from lp.registry.interfaces.person import (
87 IPersonSet,88 IPersonSet,
88 validate_public_person,89 validate_public_person,
@@ -271,10 +272,17 @@
271272
272 if component_name is None:273 if component_name is None:
273 return None274 return None
275 elif component_name.isdigit():
276 component_id = int(component_name)
277 return Store.of(self).find(
278 BugTrackerComponent,
279 BugTrackerComponent.id == component_id,
280 BugTrackerComponent.component_group == self.id).one()
274 else:281 else:
275 return Store.of(self).find(282 return Store.of(self).find(
276 BugTrackerComponent,283 BugTrackerComponent,
277 (BugTrackerComponent.name == component_name)).one()284 BugTrackerComponent.name == component_name,
285 BugTrackerComponent.component_group == self.id).one()
278286
279 def addCustomComponent(self, component_name):287 def addCustomComponent(self, component_name):
280 """Adds a component locally that isn't synced from a remote tracker288 """Adds a component locally that isn't synced from a remote tracker
@@ -680,11 +688,27 @@
680 """See `IBugTracker`."""688 """See `IBugTracker`."""
681 component_group = None689 component_group = None
682 store = IStore(BugTrackerComponentGroup)690 store = IStore(BugTrackerComponentGroup)
683 component_group = store.find(691 if component_group_name.isdigit():
684 BugTrackerComponentGroup,692 component_group_id = int(component_group_name)
685 name = component_group_name).one()693 component_group = store.find(
694 BugTrackerComponentGroup,
695 id = component_group_id).one()
696 else:
697 component_group = store.find(
698 BugTrackerComponentGroup,
699 name = component_group_name).one()
686 return component_group700 return component_group
687701
702 def getRemoteComponentForDistroSourcePackageName(
703 self, distribution, sourcepackagename):
704 """See `IBugTracker`."""
705 pkgs = Store.of(self).find(
706 BugTrackerComponent,
707 BugTrackerComponent.distribution == distribution.id,
708 BugTrackerComponent.source_package_name ==
709 sourcepackagename.id).one()
710 return pkgs[0]
711
688712
689class BugTrackerSet:713class BugTrackerSet:
690 """Implements IBugTrackerSet for a container or set of BugTrackers,714 """Implements IBugTrackerSet for a container or set of BugTrackers,
691715
=== 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-02 23:32:14 +0000
@@ -34,6 +34,9 @@
34 <div class="yui-g">34 <div class="yui-g">
35 <div class="first yui-u">35 <div class="first yui-u">
36 <div tal:replace="structure context/@@+portlet-details" />36 <div tal:replace="structure context/@@+portlet-details" />
37 <div tal:condition="features/bugtracker_components">
38 <div tal:replace="structure context/@@+portlet-components" />
39 </div>
37 </div>40 </div>
38 <div class="yui-u">41 <div class="yui-u">
39 <div tal:replace="structure context/@@+portlet-projects" />42 <div tal:replace="structure context/@@+portlet-projects" />
4043
=== 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-02 23:32:14 +0000
@@ -0,0 +1,37 @@
1<div
2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 class="portlet" id="portlet-components">
6 <h2>Components</h2>
7 <p>
8 You can link components from this bug tracker to their corresponding
9 distribution source packages in the project's &ldquo;Change
10 components&rdquo; page.
11 </p>
12 <ul tal:define="related_component_groups view/related_component_groups">
13 <li tal:repeat="component_group related_component_groups">
14 <strong><span tal:replace="component_group/name" /></strong>
15 <ul tal:define="components component_group/components">
16 <li tal:repeat="component components">
17 <span tal:replace="component/name" />
18 &nbsp;
19 <span tal:condition="component/distro_source_package">
20 <a class="sprite edit"
21 tal:attributes="href string:${context/fmt:url}/+components/${component_group/id}/${component/id}/+edit">
22 <span tal:replace="structure component/distro_source_package/name"/></a>
23 </span>
24 <a class="sprite add"
25 tal:condition="not: component/distro_source_package"
26 tal:attributes="href string:${context/fmt:url}/+components/${component_group/id}/${component/id}/+edit"></a>
27 </li>
28 <li tal:condition="not: components">
29 <i>This bug tracker has no components for this group</i>
30 </li>
31 </ul>
32 </li>
33 <li tal:condition="not: related_component_groups">
34 <i>This bug tracker has no components</i>
35 </li>
36 </ul>
37</div>
038
=== 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-02 23:32:14 +0000
@@ -88,15 +88,26 @@
8888
89 def test_link_distro_source_package(self):89 def test_link_distro_source_package(self):
90 """Check that a link can be set to a distro source package"""90 """Check that a link can be set to a distro source package"""
91 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
91 component = self.factory.makeBugTrackerComponent(92 component = self.factory.makeBugTrackerComponent(
92 u'example', self.comp_group)93 u'example', self.comp_group)
93 package = self.factory.makeDistributionSourcePackage()94 package = self.factory.makeDistributionSourcePackage()
94 self.assertIs(None, component.distro_source_package)95 self.assertIs(None, component.distro_source_package)
9596
97 # No components link to the source package yet
98 link_comp = self.bug_tracker.getRemoteComponentForDistroSourcePackageName(
99 ubuntu, component.distro_source_package.name)
100 self.assertIs(None, link_comp)
101
96 # Set the source package on the component102 # Set the source package on the component
97 component.distro_source_package = package103 component.distro_source_package = package
98 self.assertIsNot(None, component.distro_source_package)104 self.assertIsNot(None, component.distro_source_package)
99105
106 # Verify we can find the component by the source package now
107 link_comp = self.bug_tracker.getRemoteComponentForDistroSourcePackageName(
108 ubuntu, component.distro_source_package.name)
109 self.assertIsNot(None, link_comp)
110
100111
101class TestBugTrackerWithComponents(TestCaseWithFactory):112class TestBugTrackerWithComponents(TestCaseWithFactory):
102113

Subscribers

People subscribed via source and target branches

to status/vote changes: