launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #20681
[Merge] lp:~cjwatson/launchpad/bmp-buglinktarget-ui into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/bmp-buglinktarget-ui into lp:launchpad with lp:~cjwatson/launchpad/bug-bmp-activity as a prerequisite.
Commit message:
Add UI and export webservice API for linking merge proposals to bugs.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1492926 in Launchpad itself: "Git merge proposals can't be linked to bugs"
https://bugs.launchpad.net/launchpad/+bug/1492926
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/bmp-buglinktarget-ui/+merge/298368
Add UI and export webservice API for linking merge proposals to bugs.
Bugs display links to MPs where they exist; MPs have their "Related bugs and blueprints" summary section split into "Related bugs" and "Related blueprints" so that the former can more reasonably have a "Link a bug report" option. There's currently no way to add a link from the bug side in the UI, although that could be done later.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/bmp-buglinktarget-ui into lp:launchpad.
=== modified file 'lib/lp/_schema_circular_imports.py'
--- lib/lp/_schema_circular_imports.py 2016-06-25 09:54:49 +0000
+++ lib/lp/_schema_circular_imports.py 2016-06-25 09:54:49 +0000
@@ -283,6 +283,8 @@
IBranchMergeProposal, 'votes', ICodeReviewVoteReference)
patch_collection_return_type(
IBranchMergeProposal, 'getRelatedBugTasks', IBugTask)
+patch_plain_parameter_type(IBranchMergeProposal, 'linkBug', 'bug', IBug)
+patch_plain_parameter_type(IBranchMergeProposal, 'unlinkBug', 'bug', IBug)
patch_collection_return_type(IHasBranches, 'getBranches', IBranch)
patch_collection_return_type(
@@ -682,8 +684,12 @@
patch_plain_parameter_type(
IBug, 'getNominations', 'target', IBugTarget)
patch_collection_property(IBug, 'linked_merge_proposals', IBranchMergeProposal)
+patch_plain_parameter_type(
+ IBug, 'linkMergeProposal', 'merge_proposal', IBranchMergeProposal)
patch_choice_parameter_type(
IBug, 'subscribe', 'level', BugNotificationLevel)
+patch_plain_parameter_type(
+ IBug, 'unlinkMergeProposal', 'merge_proposal', IBranchMergeProposal)
# IFrontPageBugAddForm
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2016-01-26 15:47:37 +0000
+++ lib/lp/bugs/browser/bugtask.py 2016-06-25 09:54:49 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""IBugTask-related browser views."""
@@ -812,6 +812,12 @@
eager_load=True))
return linked_branches
+ @cachedproperty
+ def linked_merge_proposals(self):
+ """Filter out the links to non-visible private MPs."""
+ return list(self.context.bug.getVisibleLinkedMergeProposals(
+ self.user, eager_load=True))
+
@property
def days_to_expiration(self):
"""Return the number of days before the bug is expired, or None."""
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2016-06-25 09:54:49 +0000
+++ lib/lp/bugs/interfaces/bug.py 2016-06-25 09:54:49 +0000
@@ -817,6 +817,27 @@
"""
@call_with(user=REQUEST_USER)
+ @operation_parameters(
+ # Really IBranchMergeProposal, patched in _schema_circular_imports.py.
+ merge_proposal=Reference(
+ Interface, title=_('Merge proposal'), required=True))
+ @export_write_operation()
+ @operation_for_version('devel')
+ def linkMergeProposal(merge_proposal, user, check_permissions=True):
+ """Ensure that this MP is linked to this bug."""
+
+ @call_with(user=REQUEST_USER)
+ @operation_parameters(
+ # Really IBranchMergeProposal, patched in _schema_circular_imports.py.
+ merge_proposal=Reference(
+ Interface, title=_('Merge proposal'), required=True))
+ @export_write_operation()
+ @operation_for_version('devel')
+ def unlinkMergeProposal(merge_proposal, user, check_permissions=True):
+ """Ensure that any links between this bug and the given MP are removed.
+ """
+
+ @call_with(user=REQUEST_USER)
@operation_parameters(cve=Reference(ICve, title=_('CVE'), required=True))
@export_write_operation()
def linkCVE(cve, user, check_permissions=True):
=== modified file 'lib/lp/bugs/templates/bugtask-index.pt'
--- lib/lp/bugs/templates/bugtask-index.pt 2014-11-29 06:11:18 +0000
+++ lib/lp/bugs/templates/bugtask-index.pt 2016-06-25 09:54:49 +0000
@@ -206,8 +206,9 @@
<div id="bug-branches-container"
style="float: left">
<tal:branches
- define="linked_branches view/linked_branches"
- condition="linked_branches">
+ define="linked_branches view/linked_branches;
+ linked_merge_proposals view/linked_merge_proposals"
+ condition="python: linked_branches or linked_merge_proposals">
<div id="bug-branches">
<h2>Related branches</h2>
@@ -215,6 +216,12 @@
<tal:bug-branches repeat="linked_branch linked_branches">
<tal:bug-branch replace="structure linked_branch/@@+bug-branch" />
</tal:bug-branches>
+ <tal:bug-mps repeat="linked_merge_proposal linked_merge_proposals">
+ <tal:bug-mp define="proposal linked_merge_proposal;
+ bug context/bug">
+ <metal:bug-mp use-macro="linked_merge_proposal/@@+bmp-macros/bug-summary" />
+ </tal:bug-mp>
+ </tal:bug-mps>
</div>
</tal:branches>
</div><!-- bug-branch-container -->
=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
--- lib/lp/code/browser/branchmergeproposal.py 2016-05-14 09:54:32 +0000
+++ lib/lp/code/browser/branchmergeproposal.py 2016-06-25 09:54:49 +0000
@@ -283,6 +283,10 @@
BranchMergeProposalStatus.SUPERSEDED)
return Link('+resubmit', text, enabled=enabled, icon='edit')
+ def link_bug(self):
+ text = 'Link a bug report'
+ return Link('+linkbug', text, icon='add')
+
class BranchMergeProposalEditMenu(NavigationMenu,
BranchMergeProposalMenuMixin):
@@ -308,6 +312,7 @@
'set_commit_message',
'set_description',
'edit_status',
+ 'link_bug',
'merge',
'request_review',
'resubmit',
@@ -704,10 +709,9 @@
return self.context.preview_diff
@property
- def has_bug_or_spec(self):
- """Return whether or not the merge proposal has a linked bug or spec.
- """
- return self.linked_bugtasks or self.spec_links
+ def has_specs(self):
+ """Return whether the merge proposal has any linked specs."""
+ return bool(self.spec_links)
@property
def spec_links(self):
@@ -720,7 +724,7 @@
@cachedproperty
def linked_bugtasks(self):
- """Return BugTasks linked to the source branch."""
+ """Return BugTasks linked to the MP or the source branch."""
return self.context.getRelatedBugTasks(self.user)
@property
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2016-05-14 09:54:32 +0000
+++ lib/lp/code/browser/configure.zcml 2016-06-25 09:54:49 +0000
@@ -254,6 +254,18 @@
permission="launchpad.Edit"
template="../../app/templates/generic-edit.pt"/>
<browser:page
+ name="+linkbug"
+ for="lp.code.interfaces.branchmergeproposal.IBranchMergeProposal"
+ class="lp.bugs.browser.buglinktarget.BugLinkView"
+ permission="launchpad.AnyPerson"
+ template="../../app/templates/generic-edit.pt"/>
+ <browser:page
+ name="+unlinkbug"
+ for="lp.code.interfaces.branchmergeproposal.IBranchMergeProposal"
+ class="lp.bugs.browser.buglinktarget.BugsUnlinkView"
+ permission="launchpad.AnyPerson"
+ template="../templates/branchmergeproposal-unlinkbugs.pt"/>
+ <browser:page
name="+link-summary"
for="lp.code.interfaces.branchmergeproposal.IBranchMergeProposal"
class="lp.code.browser.branchmergeproposal.BranchMergeCandidateView"
=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2016-06-03 14:08:52 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2016-06-25 09:54:49 +0000
@@ -1,7 +1,6 @@
# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
-
"""Unit tests for BranchMergeProposals."""
__metaclass__ = type
@@ -11,9 +10,11 @@
timedelta,
)
from difflib import unified_diff
+import doctest
import hashlib
import re
+from fixtures import FakeLogger
from lazr.lifecycle.event import ObjectModifiedEvent
from lazr.restful.interfaces import IJSONRequestCache
import pytz
@@ -23,6 +24,7 @@
Tag,
)
from testtools.matchers import (
+ DocTestMatches,
Equals,
Is,
MatchesDict,
@@ -81,6 +83,7 @@
from lp.services.webapp.interfaces import BrowserNotificationLevel
from lp.services.webapp.servers import LaunchpadTestRequest
from lp.testing import (
+ admin_logged_in,
BrowserTestCase,
EventRecorder,
feature_flags,
@@ -102,6 +105,7 @@
from lp.testing.pages import (
extract_text,
find_tag_by_id,
+ find_tags_by_class,
first_tag_by_class,
get_feedback_messages,
)
@@ -1396,7 +1400,7 @@
return PreviewDiff.create(
self.bmp, preview_diff_bytes, u'a', u'b', None, u'')
- def test_linked_bugs_excludes_mutual_bugs(self):
+ def test_linked_bugtasks_excludes_mutual_bugs(self):
"""List bugs that are linked to the source only."""
bug = self.factory.makeBug()
self.bmp.source_branch.linkBug(bug, self.bmp.registrant)
@@ -1404,7 +1408,7 @@
view = create_initialized_view(self.bmp, '+index')
self.assertEqual([], view.linked_bugtasks)
- def test_linked_bugs_excludes_private_bugs(self):
+ def test_linked_bugtasks_excludes_private_bugs(self):
"""List bugs that are linked to the source only."""
bug = self.factory.makeBug()
person = self.factory.makePerson()
@@ -1416,6 +1420,15 @@
view = create_initialized_view(self.bmp, '+index')
self.assertEqual([bug.default_bugtask], view.linked_bugtasks)
+ def test_linked_bugtasks_includes_direct_links(self):
+ # linked_bugtasks includes bugs that are linked directly to the
+ # merge proposal, as is the case for Git-based MPs.
+ bug = self.factory.makeBug()
+ bmp = self.factory.makeBranchMergeProposalForGit(registrant=self.user)
+ bmp.linkBug(bug, bmp.registrant)
+ view = create_initialized_view(bmp, '+index')
+ self.assertEqual([bug.default_bugtask], view.linked_bugtasks)
+
def makeRevisionGroups(self):
review_date = datetime(2009, 9, 10, tzinfo=pytz.UTC)
bmp = self.factory.makeBranchMergeProposal(
@@ -1985,6 +1998,134 @@
InformationType.USERDATA, branch.owner, verify_policy=False)
+class TestBranchMergeProposalLinkBugViewMixin:
+
+ layer = LaunchpadFunctionalLayer
+
+ def setUp(self):
+ super(TestBranchMergeProposalLinkBugViewMixin, self).setUp()
+ self.bmp = self._makeBranchMergeProposal()
+
+ def test_anonymous(self):
+ # The "Link a bug report" link on BranchMergeProposal:+index is
+ # visible to all, but anonymous users will need to log in to use it.
+ self.useFixture(FakeLogger())
+ browser = self.getViewBrowser(
+ self.bmp, view_name="+index", no_login=True)
+ self.assertRaises(
+ Unauthorized, browser.getLink("Link a bug report").click)
+
+ def test_logged_in(self):
+ # Any logged-in user can use the "Link a bug report" link.
+ browser = self.getViewBrowser(self.bmp, view_name="+index")
+ browser.getLink("Link a bug report").click()
+ self.assertStartsWith(browser.title, "Link a bug report")
+
+ def assertBugLinks(self, bugtasks, browser):
+ expected_text = []
+ for bugtask in bugtasks:
+ expected_text.append(
+ "Bug #%d: %s\n%s\n%s" % (
+ bugtask.bug.id, bugtask.bug.title,
+ bugtask.importance.title, bugtask.status.title))
+ self.assertEqual(
+ expected_text,
+ [extract_text(tag) for tag in find_tags_by_class(
+ browser.contents, "bug-mp-summary")])
+
+ def test_link(self):
+ # A user can enter a bug number to link from an MP to a bug.
+ bug = self.factory.makeBug()
+ bugtask = bug.default_bugtask
+ browser = self.getViewBrowser(self.bmp, view_name="+linkbug")
+ browser.getControl("Bug ID").value = str(bug.id)
+ browser.getControl("Link").click()
+ with person_logged_in(self.user):
+ self.assertBugLinks([bugtask], browser)
+
+ def test_same_link_twice(self):
+ # Attempting to link to the same bug twice only creates a single
+ # link.
+ bug = self.factory.makeBug()
+ bugtask = bug.default_bugtask
+ with person_logged_in(self.user):
+ self.bmp.linkBug(bug, self.user)
+ browser = self.getViewBrowser(self.bmp, view_name="+linkbug")
+ browser.getControl("Bug ID").value = str(bug.id)
+ browser.getControl("Link").click()
+ with person_logged_in(self.user):
+ self.assertBugLinks([bugtask], browser)
+
+
+class TestBranchMergeProposalLinkBugViewBzr(
+ TestBranchMergeProposalLinkBugViewMixin, BrowserTestCase):
+
+ def _makeBranchMergeProposal(self):
+ return self.factory.makeBranchMergeProposal()
+
+
+class TestBranchMergeProposalLinkBugViewGit(
+ TestBranchMergeProposalLinkBugViewMixin, GitHostingClientMixin,
+ BrowserTestCase):
+
+ def _makeBranchMergeProposal(self):
+ return self.factory.makeBranchMergeProposalForGit()
+
+ def assertMergeProposalLinks(self, mps, browser):
+ matchers = []
+ for mp in mps:
+ matchers.append(DocTestMatches(
+ "%s\n...\nfor merging\ninto\n%s\n..." % (
+ mp.merge_source.identity, mp.merge_target.identity),
+ flags=doctest.ELLIPSIS))
+ self.assertThat(
+ [extract_text(tag) for tag in find_tags_by_class(
+ browser.contents, "bug-mp-summary")],
+ MatchesListwise(matchers))
+
+ def test_bug_page_shows_link(self):
+ # The bug-MP link is shown on the bug page.
+ bug = self.factory.makeBug()
+ title = bug.title
+ with person_logged_in(self.user):
+ self.bmp.linkBug(bug, self.user)
+ browser = self.getViewBrowser(self.bmp)
+ browser.getLink(title).click()
+ with person_logged_in(self.user):
+ self.assertMergeProposalLinks([self.bmp], browser)
+
+ def test_link_to_private_bug_only_shown_if_visible(self):
+ # The MP page only shows links to private bugs if the user can see
+ # the bugs in question.
+ bug = self.factory.makeBug(information_type=InformationType.USERDATA)
+ with admin_logged_in() as admin:
+ bugtask = bug.default_bugtask
+ self.bmp.linkBug(bug, admin)
+ admin_browser = self.getViewBrowser(self.bmp, user=admin)
+ with admin_logged_in():
+ self.assertBugLinks([bugtask], admin_browser)
+ user_browser = self.getViewBrowser(self.bmp)
+ self.assertBugLinks([], user_browser)
+
+ def test_unlink_from_merge_proposal(self):
+ # The MP page has a delete button to unlink the bug.
+ bug = self.factory.makeBug()
+ with person_logged_in(self.user):
+ self.bmp.linkBug(bug, self.user)
+ browser = self.getViewBrowser(self.bmp)
+ browser.getLink(url="+unlinkbug").click()
+ self.assertBugLinks([], browser)
+
+ def test_unlink_from_bug(self):
+ # The bug page has a delete button to unlink the MP.
+ bug = self.factory.makeBug()
+ with person_logged_in(self.user):
+ self.bmp.linkBug(bug, self.user)
+ browser = self.getViewBrowser(bug)
+ browser.getLink(url="+unlinkbug").click()
+ self.assertMergeProposalLinks([], browser)
+
+
class TestBranchMergeProposalDeleteViewMixin:
"""Test the BranchMergeProposal deletion view."""
=== modified file 'lib/lp/code/interfaces/branchmergeproposal.py'
--- lib/lp/code/interfaces/branchmergeproposal.py 2016-06-25 09:54:49 +0000
+++ lib/lp/code/interfaces/branchmergeproposal.py 2016-06-25 09:54:49 +0000
@@ -695,6 +695,24 @@
:param comments: The comments.
"""
+ @export_write_operation()
+ @operation_parameters(
+ # Really IBug, patched in _schema_circular_imports.py.
+ bug=Reference(schema=Interface))
+ @call_with(user=REQUEST_USER)
+ @operation_for_version('devel')
+ def linkBug(bug, user=None, check_permissions=True):
+ """Link a bug to this merge proposal."""
+
+ @export_write_operation()
+ @operation_parameters(
+ # Really IBug, patched in _schema_circular_imports.py.
+ bug=Reference(schema=Interface))
+ @call_with(user=REQUEST_USER)
+ @operation_for_version('devel')
+ def unlinkBug(bug, user=None, check_permissions=True):
+ """Unlink a bug from this merge proposal."""
+
class IBranchMergeProposal(IBranchMergeProposalPublic,
IBranchMergeProposalView, IBranchMergeProposalEdit,
=== modified file 'lib/lp/code/javascript/branch.bugspeclinks.js'
--- lib/lp/code/javascript/branch.bugspeclinks.js 2012-08-09 04:56:41 +0000
+++ lib/lp/code/javascript/branch.bugspeclinks.js 2016-06-25 09:54:49 +0000
@@ -1,4 +1,4 @@
-/* Copyright 2012 Canonical Ltd. This software is licensed under the
+/* Copyright 2012-2016 Canonical Ltd. This software is licensed under the
* GNU Affero General Public License version 3 (see the file LICENSE).
*
* Provide functionality for picking a bug.
@@ -12,9 +12,20 @@
var superclass = Y.lp.bugs.bug_picker.BugPicker;
/*
- * Extract the best candidate for a bug number from the branch name.
+ * Extract the best candidate for a bug number from the context.
*/
-namespace.extract_candidate_bug_id = function(branch_name) {
+namespace.extract_candidate_bug_id = function(context) {
+ var branch_name;
+ if (context.source_branch !== undefined) {
+ branch_name = context.source_branch.name;
+ } else if (context.source_git_path !== undefined) {
+ branch_name = context.source_git_path;
+ if (branch_name.indexOf('refs/heads/') === 0) {
+ branch_name = branch_name.slice('refs/heads/'.length);
+ }
+ } else {
+ branch_name = context.name;
+ }
// Extract all the runs of numbers in the branch name and sort by
// descending length.
var chunks = branch_name.split(/\D/g).sort(function (a, b) {
@@ -61,7 +72,7 @@
this.after('visibleChange', function() {
if (this.get('visible')) {
var guessed_bug_id =
- namespace.extract_candidate_bug_id(LP.cache.context.name);
+ namespace.extract_candidate_bug_id(LP.cache.context);
if (Y.Lang.isValue(guessed_bug_id)) {
this._search_input.set('value', guessed_bug_id);
// Select the pre-filled bug number (if any) so that it will
=== modified file 'lib/lp/code/javascript/tests/test_bugspeclinks.js'
--- lib/lp/code/javascript/tests/test_bugspeclinks.js 2015-10-22 00:13:43 +0000
+++ lib/lp/code/javascript/tests/test_bugspeclinks.js 2016-06-25 09:54:49 +0000
@@ -1,4 +1,4 @@
-/* Copyright 2012-2012 Canonical Ltd. This software is licensed under the
+/* Copyright 2012-2016 Canonical Ltd. This software is licensed under the
* GNU Affero General Public License version 3 (see the file LICENSE). */
YUI.add('lp.code.branch.bugspeclinks.test', function (Y) {
@@ -14,48 +14,74 @@
test_no_bug_id_present: function() {
// If nothing that looks like a bug ID is present, null is
// returned.
- Y.Assert.isNull(extract_candidate_bug_id('no-id-here'));
+ Y.Assert.isNull(extract_candidate_bug_id({'name': 'no-id-here'}));
},
test_short_digit_rund_ignored: function() {
- Y.Assert.isNull(extract_candidate_bug_id('foo-1234-bar'));
+ Y.Assert.isNull(
+ extract_candidate_bug_id({'name': 'foo-1234-bar'}));
},
test_leading_zeros_disqualify_potential_ids: function() {
// Since bug IDs can't start with zeros, any string of numbers
// with a leading zero are not considered as a potential ID.
- Y.Assert.isNull(extract_candidate_bug_id('foo-0123456-bar'));
+ Y.Assert.isNull(
+ extract_candidate_bug_id({'name': 'foo-0123456-bar'}));
Y.Assert.areEqual(
- extract_candidate_bug_id('foo-0123456-999999-bar'), '999999');
+ extract_candidate_bug_id({'name': 'foo-0123456-999999-bar'}),
+ '999999');
},
test_five_digit_bug_ids_are_extracted: function() {
Y.Assert.areEqual(
- extract_candidate_bug_id('foo-12345-bar'), '12345');
+ extract_candidate_bug_id({'name': 'foo-12345-bar'}), '12345');
},
test_six_digit_bug_ids_are_extracted: function() {
Y.Assert.areEqual(
- extract_candidate_bug_id('foo-123456-bar'), '123456');
+ extract_candidate_bug_id({'name': 'foo-123456-bar'}),
+ '123456');
},
test_seven_digit_bug_ids_are_extracted: function() {
Y.Assert.areEqual(
- extract_candidate_bug_id('foo-1234567-bar'), '1234567');
+ extract_candidate_bug_id({'name': 'foo-1234567-bar'}),
+ '1234567');
},
test_eight_digit_bug_ids_are_extracted: function() {
Y.Assert.areEqual(
- extract_candidate_bug_id('foo-12345678-bar'), '12345678');
+ extract_candidate_bug_id({'name': 'foo-12345678-bar'}),
+ '12345678');
},
test_longest_potential_id_is_extracted: function() {
// Since there may be numbers other than a bug ID in a branch
// name, we want to extract the longest string of digits.
Y.Assert.areEqual(
- extract_candidate_bug_id('bug-123456-take-2'), '123456');
- Y.Assert.areEqual(
- extract_candidate_bug_id('123456-1234567'), '1234567');
+ extract_candidate_bug_id({'name': 'bug-123456-take-2'}),
+ '123456');
+ Y.Assert.areEqual(
+ extract_candidate_bug_id({'name': '123456-1234567'}),
+ '1234567');
+ },
+
+ test_merge_proposal_bzr: function() {
+ // If the context is a Bazaar-based merge proposal, bug IDs are
+ // extracted from the source branch.
+ Y.Assert.areEqual(
+ extract_candidate_bug_id(
+ {'source_branch': {'name': 'foo-123456-bar'}}),
+ '123456');
+ },
+
+ test_merge_proposal_git: function() {
+ // If the context is a Git-based merge proposal, bug IDs are
+ // extracted from the source reference path.
+ Y.Assert.areEqual(
+ extract_candidate_bug_id(
+ {'source_git_path': 'refs/heads/foo-123456-bar'}),
+ '123456');
}
}));
=== modified file 'lib/lp/code/stories/branches/xx-branchmergeproposals.txt'
--- lib/lp/code/stories/branches/xx-branchmergeproposals.txt 2015-06-23 17:30:39 +0000
+++ lib/lp/code/stories/branches/xx-branchmergeproposals.txt 2016-06-25 09:54:49 +0000
@@ -88,6 +88,8 @@
lp://dev/~name12/gnome-terminal/pushed
To merge this branch:
bzr merge lp://dev/~name12/gnome-terminal/klingon
+ Related bugs:
+ Link a bug report
Editing a commit message
@@ -457,23 +459,25 @@
in the source branch.
>>> def print_bugs_and_specs(browser):
- ... links = find_tag_by_id(browser.contents,
- ... 'related-bugs-and-blueprints')
- ... if links == None:
- ... print links
- ... else:
- ... print extract_text(links)
+ ... for id in 'related-bugs', 'related-blueprints':
+ ... links = find_tag_by_id(browser.contents, id)
+ ... if links == None:
+ ... print links
+ ... else:
+ ... print extract_text(links)
>>> login('admin@xxxxxxxxxxxxx')
>>> bmp = factory.makeBranchMergeProposal()
>>> bmp_url = canonical_url(bmp)
>>> logout()
-There shouldn't be a 'Linked bug reports and blueprints' section if there are
-none
+If there are no related bugs, the corresponding section should only show a
+"Link to bug report" link; if there are no related blueprints, there should
+be no corresponding section.
>>> nopriv_browser.open(bmp_url)
>>> print_bugs_and_specs(nopriv_browser)
+ Related bugs: Link a bug report
None
>>> login('admin@xxxxxxxxxxxxx')
@@ -485,7 +489,8 @@
>>> nopriv_browser.open(bmp_url)
>>> print_bugs_and_specs(nopriv_browser)
- Related bugs and blueprints: Bug #...: Bug for linking Undecided New
+ Related bugs: Bug #...: Bug for linking Undecided New Link a bug report
+ None
Target branch edge cases
=== modified file 'lib/lp/code/templates/branchmergeproposal-macros.pt'
--- lib/lp/code/templates/branchmergeproposal-macros.pt 2015-05-12 17:14:52 +0000
+++ lib/lp/code/templates/branchmergeproposal-macros.pt 2016-06-25 09:54:49 +0000
@@ -67,4 +67,63 @@
</metal:active-reviews>
+<metal:bug-summary define-macro="bug-summary">
+
+ <tal:comment condition="nothing">
+ This macro requires the following defined variables:
+ proposal - the linked merge proposal
+ bug - the linked bug
+ </tal:comment>
+
+ <div class="bug-mp-summary"
+ tal:define="show_edit bug/required:launchpad.Edit"
+ tal:condition="proposal/required:launchpad.View">
+ <a tal:attributes="href proposal/merge_source/fmt:url"
+ class="sprite branch"
+ tal:content="proposal/merge_source/display_name"/>
+ <a title="Remove link"
+ tal:condition="show_edit"
+ tal:attributes="href string:${proposal/fmt:url}/+unlinkbug?field.bugs=${bug/id}">
+ <img src="/@@/remove" alt="Remove"/>
+ </a>
+ <div class="reviews">
+ <tal:merge-fragment
+ tal:replace="structure proposal/@@+summary-fragment"/>
+ </div>
+ </div>
+
+</metal:bug-summary>
+
+<metal:bug-links define-macro="bug-links">
+
+ <table tal:condition="view/linked_bugtasks">
+ <tal:bug-tasks repeat="bugtask view/linked_bugtasks">
+ <tr tal:condition="bugtask/bug/required:launchpad.View"
+ tal:attributes="id string:buglink-${bugtask/bug/id}"
+ class="bug-mp-summary">
+ <td tal:content="structure bugtask/fmt:link:bugs" class="first"/>
+ <td>
+ <span tal:content="bugtask/importance/title"
+ tal:attributes="class string:importance${bugtask/importance/name}"
+ >Critical</span>
+ </td>
+ <td>
+ <span tal:content="bugtask/status/title"
+ tal:attributes="class string:status${bugtask/status/name}"
+ >Triaged</span>
+ </td>
+ <td>
+ <a title="Remove link"
+ class="delete-buglink"
+ tal:attributes="href string:+unlinkbug?field.bugs=${bugtask/bug/id};
+ id string:delete-buglink-${bugtask/bug/id}">
+ <img src="/@@/remove" alt="Remove"/>
+ </a>
+ </td>
+ </tr>
+ </tal:bug-tasks>
+ </table>
+
+</metal:bug-links>
+
</tal:root>
=== modified file 'lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt'
--- lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt 2015-08-28 15:03:12 +0000
+++ lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt 2016-06-25 09:54:49 +0000
@@ -129,11 +129,40 @@
<th>To merge this branch:</th>
<td>bzr merge <span class="branch-url" tal:content="context/source_branch/bzr_identity" /></td>
</tr>
- <tr id="related-bugs-and-blueprints" tal:condition="view/has_bug_or_spec" >
- <th>Related bugs and blueprints:</th>
+ <tr id="related-bugs">
+ <th>Related bugs:</th>
+ <td>
+ <metal:bug-links use-macro="context/@@+bmp-macros/bug-links"/>
+ <div tal:define="link context_menu/link_bug"
+ tal:condition="link/enabled">
+ <a id="linkbug"
+ class="sprite add"
+ tal:attributes="href link/url"
+ tal:content="link/text" />
+ </div>
+ <script type="text/javascript">
+ LPJS.use('io-base', 'lp.code.branch.bugspeclinks', function(Y) {
+ Y.on('domready', function() {
+ var logged_in = LP.links['me'] !== undefined;
+
+ if (logged_in) {
+ var config = {
+ picker_activator = '#linkbug'
+ };
+ var linked_bug_picker = new
+ Y.lp.code.branch.bugspeclinks.LinkedBugPicker(config);
+ linked_bug_picker.render();
+ linked_bug_picker.hide();
+ }
+ });
+ });
+ </script>
+ </td>
+ </tr>
+ <tr id="related-blueprints" tal:condition="view/has_specs">
+ <th>Related blueprints:</th>
<td tal:define="branch context/source_branch">
- <metal:bug-branch-links use-macro="branch/@@+macros/bug-branch-links"/>
- <metal:spec-branch-links use-macro="branch/@@+macros/spec-branch-links"/>
+ <metal:spec-branch-links use-macro="branch/@@+macros/spec-branch-links"/>
</td>
</tr>
</tbody>
=== added file 'lib/lp/code/templates/branchmergeproposal-unlinkbugs.pt'
--- lib/lp/code/templates/branchmergeproposal-unlinkbugs.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/code/templates/branchmergeproposal-unlinkbugs.pt 2016-06-25 09:54:49 +0000
@@ -0,0 +1,27 @@
+<html
+ 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"
+ xml:lang="en"
+ lang="en"
+ dir="ltr"
+ metal:use-macro="context/@@unlinkbugs_template/master"
+ i18n:domain="launchpad">
+
+ <body>
+ <div class="documentDescription" metal:fill-slot="extra_info">
+ <div>
+ <a tal:attributes="href context/fmt:url">
+ <strong>
+ <tal:merge-source replace="context/merge_source/identity"/>
+ </strong>
+ ⇒
+ <tal:merge-target replace="context/merge_target/identity"/>
+ </a>
+ </div>
+ This will <em>remove</em> the link between the merge proposal and the
+ selected bug reports.
+ </div>
+ </body>
+</html>
Follow ups