launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18540
[Merge] lp:~cjwatson/launchpad/git-activereviews into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/git-activereviews into lp:launchpad.
Commit message:
Extend merge proposal listings to cover Git, and add GitRef:+activereviews.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1453020 in Launchpad itself: "Please add support for +activereviews for git branches"
https://bugs.launchpad.net/launchpad/+bug/1453020
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-activereviews/+merge/258910
Extend merge proposal listings to cover Git, and add GitRef:+activereviews.
We should probably have GitRepository:+activereviews at some point too, but that can come in a separate branch.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-activereviews into lp:launchpad.
=== modified file 'lib/lp/code/browser/branchmergeproposallisting.py'
--- lib/lp/code/browser/branchmergeproposallisting.py 2014-12-08 00:32:30 +0000
+++ lib/lp/code/browser/branchmergeproposallisting.py 2015-05-12 17:28:52 +0000
@@ -50,6 +50,10 @@
IBranchMergeProposalGetter,
IBranchMergeProposalListingBatchNavigator,
)
+from lp.code.interfaces.gitcollection import (
+ IAllGitRepositories,
+ IGitCollection,
+ )
from lp.code.interfaces.hasbranches import IHasMergeProposals
from lp.services.config import config
from lp.services.propertycache import (
@@ -168,7 +172,7 @@
@cachedproperty
def proposals(self):
- """Return a list of BranchListingItems."""
+ """Return a list of BranchMergeProposalListingItems."""
proposals = self._proposals_for_current_batch
return [self._createItem(proposal) for proposal in proposals]
@@ -287,12 +291,11 @@
def getProposals(self):
"""Get the proposals for the view."""
- collection = IBranchCollection(self.context)
- collection = collection.visibleByUser(self.user)
- proposals = collection.getMergeProposals(
- [BranchMergeProposalStatus.CODE_APPROVED,
- BranchMergeProposalStatus.NEEDS_REVIEW], eager_load=True)
- return proposals
+ return self.context.getMergeProposals(
+ status=(
+ BranchMergeProposalStatus.CODE_APPROVED,
+ BranchMergeProposalStatus.NEEDS_REVIEW),
+ visible_by_user=self.user, eager_load=True)
def _getReviewGroup(self, proposal, votes, reviewer):
"""One of APPROVED, MINE, TO_DO, CAN_DO, ARE_DOING, OTHER or WIP.
@@ -324,8 +327,8 @@
return self.WIP
if (reviewer is not None and
- (proposal.source_branch.owner == reviewer or
- (reviewer.inTeam(proposal.source_branch.owner) and
+ (proposal.merge_source.owner == reviewer or
+ (reviewer.inTeam(proposal.merge_source.owner) and
proposal.registrant == reviewer))):
return self.MINE
@@ -437,15 +440,13 @@
def _getReviewer(self):
return self.context
- def _getCollection(self):
- return getUtility(IAllBranches)
-
- def getProposals(self):
+ def getProposals(self, project=None):
"""See `ActiveReviewsView`."""
- collection = self._getCollection().visibleByUser(self.user)
- return collection.getMergeProposalsForPerson(
- self._getReviewer(), [BranchMergeProposalStatus.CODE_APPROVED,
- BranchMergeProposalStatus.NEEDS_REVIEW], eager_load=True)
+ return self._getReviewer().getOwnedAndRequestedReviews(
+ status=(
+ BranchMergeProposalStatus.CODE_APPROVED,
+ BranchMergeProposalStatus.NEEDS_REVIEW),
+ visible_by_user=self.user, project=project, eager_load=True)
class PersonProductActiveReviewsView(PersonActiveReviewsView):
@@ -459,8 +460,9 @@
def _getReviewer(self):
return self.context.person
- def _getCollection(self):
- return getUtility(IAllBranches).inProduct(self.context.product)
+ def getProposals(self):
+ return super(PersonProductActiveReviewsView, self).getProposals(
+ project=self.context.product)
@property
def no_proposal_message(self):
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2015-04-28 16:48:22 +0000
+++ lib/lp/code/browser/configure.zcml 2015-05-12 17:28:52 +0000
@@ -844,6 +844,12 @@
name="+register-merge"
permission="launchpad.AnyPerson"
template="../templates/gitref-register-merge.pt"/>
+ <browser:page
+ for="lp.code.interfaces.gitref.IGitRef"
+ class="lp.code.browser.branchmergeproposallisting.BranchActiveReviewsView"
+ permission="zope.Public"
+ name="+activereviews"
+ template="../templates/active-reviews.pt"/>
<browser:menus
classes="GitRefContextMenu"
module="lp.code.browser.gitref"/>
=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposallisting.py'
--- lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2013-04-29 00:10:09 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2015-05-12 17:28:52 +0000
@@ -12,6 +12,7 @@
from testtools.content_type import UTF8_TEXT
from testtools.matchers import Equals
import transaction
+from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
from lp.app.enums import InformationType
@@ -23,8 +24,14 @@
BranchMergeProposalStatus,
CodeReviewVote,
)
+from lp.code.interfaces.gitref import IGitRef
+from lp.code.interfaces.gitrepository import (
+ GIT_FEATURE_FLAG,
+ IGitRepositorySet,
+ )
from lp.registry.model.personproduct import PersonProduct
from lp.services.database.sqlbase import flush_database_caches
+from lp.services.features.testing import FeatureFixture
from lp.testing import (
ANONYMOUS,
BrowserTestCase,
@@ -45,7 +52,55 @@
_default = object()
-class TestProposalVoteSummary(TestCaseWithFactory):
+class BzrMixin:
+ """Mixin for Bazaar-based tests."""
+
+ def _makeBranch(self, target=None, **kwargs):
+ if target is not None:
+ # This only handles projects at the moment.
+ kwargs["product"] = target
+ return self.factory.makeBranch(**kwargs)
+
+ def _makePackageBranch(self, **kwargs):
+ return self.factory.makePackageBranch(**kwargs)
+
+ def _makeStackedOnBranchChain(self, target=None, **kwargs):
+ if target is not None:
+ # This only handles projects at the moment.
+ kwargs["product"] = target
+ return self.factory.makeStackedOnBranchChain(**kwargs)
+
+ def _makeBranchMergeProposal(self, target=None, merge_target=None,
+ **kwargs):
+ # This only handles projects at the moment.
+ return self.factory.makeBranchMergeProposal(
+ product=target, target_branch=merge_target, **kwargs)
+
+
+class GitMixin:
+ """Mixin for Git-based tests."""
+
+ def setUp(self, user=ANONYMOUS):
+ super(GitMixin, self).setUp(user=user)
+ self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
+
+ def _makeBranch(self, **kwargs):
+ return self.factory.makeGitRefs(**kwargs)[0]
+
+ def _makePackageBranch(self, **kwargs):
+ dsp = self.factory.makeDistributionSourcePackage()
+ return self.factory.makeGitRefs(target=dsp, **kwargs)[0]
+
+ def _makeStackedOnBranchChain(self, depth=None, **kwargs):
+ # Git doesn't have stacked branches. Just make an ordinary reference.
+ return self._makeBranch(**kwargs)
+
+ def _makeBranchMergeProposal(self, merge_target=None, **kwargs):
+ return self.factory.makeBranchMergeProposalForGit(
+ target_ref=merge_target, **kwargs)
+
+
+class TestProposalVoteSummaryMixin:
"""The vote summary shows a summary of the current votes."""
layer = DatabaseFunctionalLayer
@@ -53,7 +108,8 @@
def setUp(self):
# Use an admin so we don't have to worry about launchpad.Edit
# permissions on the merge proposals for adding comments.
- TestCaseWithFactory.setUp(self, user="admin@xxxxxxxxxxxxx")
+ super(TestProposalVoteSummaryMixin, self).setUp(
+ user="admin@xxxxxxxxxxxxx")
def _createComment(self, proposal, reviewer=None, vote=None,
comment=_default):
@@ -69,7 +125,7 @@
def _get_vote_summary(self, proposal):
"""Return the vote summary string for the proposal."""
view = create_initialized_view(
- proposal.source_branch.owner, '+merges', rootsite='code')
+ proposal.merge_source.owner, '+merges', rootsite='code')
batch_navigator = view.proposals
# There will only be one item in the list of proposals.
[listing_item] = batch_navigator.proposals
@@ -78,14 +134,14 @@
def test_no_votes_or_comments(self):
# If there are no votes or comments, then we show that.
- proposal = self.factory.makeBranchMergeProposal()
+ proposal = self._makeBranchMergeProposal()
summary, comment_count = self._get_vote_summary(proposal)
self.assertEqual([], summary)
self.assertEqual(0, comment_count)
def test_no_votes_with_comments(self):
# The comment count is shown.
- proposal = self.factory.makeBranchMergeProposal()
+ proposal = self._makeBranchMergeProposal()
self._createComment(proposal)
summary, comment_count = self._get_vote_summary(proposal)
self.assertEqual([], summary)
@@ -93,7 +149,7 @@
def test_vote_without_comment(self):
# If there are no comments we don't show a count.
- proposal = self.factory.makeBranchMergeProposal()
+ proposal = self._makeBranchMergeProposal()
self._createComment(
proposal, vote=CodeReviewVote.APPROVE, comment=None)
summary, comment_count = self._get_vote_summary(proposal)
@@ -104,7 +160,7 @@
def test_vote_with_comment(self):
# A vote with a comment counts as a vote and a comment.
- proposal = self.factory.makeBranchMergeProposal()
+ proposal = self._makeBranchMergeProposal()
self._createComment(proposal, vote=CodeReviewVote.APPROVE)
summary, comment_count = self._get_vote_summary(proposal)
self.assertEqual(
@@ -114,7 +170,7 @@
def test_disapproval(self):
# Shown as Disapprove: <count>.
- proposal = self.factory.makeBranchMergeProposal()
+ proposal = self._makeBranchMergeProposal()
self._createComment(proposal, vote=CodeReviewVote.DISAPPROVE)
summary, comment_count = self._get_vote_summary(proposal)
self.assertEqual(
@@ -124,7 +180,7 @@
def test_abstain(self):
# Shown as Abstain: <count>.
- proposal = self.factory.makeBranchMergeProposal()
+ proposal = self._makeBranchMergeProposal()
transaction.commit()
self._createComment(proposal, vote=CodeReviewVote.ABSTAIN)
summary, comment_count = self._get_vote_summary(proposal)
@@ -135,7 +191,7 @@
def test_vote_ranking(self):
# Votes go from best to worst.
- proposal = self.factory.makeBranchMergeProposal()
+ proposal = self._makeBranchMergeProposal()
self._createComment(proposal, vote=CodeReviewVote.DISAPPROVE)
self._createComment(proposal, vote=CodeReviewVote.APPROVE)
summary, comment_count = self._get_vote_summary(proposal)
@@ -158,7 +214,7 @@
def test_multiple_votes_for_type(self):
# Multiple votes of a type are aggregated in the summary.
- proposal = self.factory.makeBranchMergeProposal()
+ proposal = self._makeBranchMergeProposal()
self._createComment(proposal, vote=CodeReviewVote.DISAPPROVE)
self._createComment(proposal, vote=CodeReviewVote.APPROVE)
self._createComment(proposal, vote=CodeReviewVote.DISAPPROVE)
@@ -178,6 +234,16 @@
self.assertEqual(4, comment_count)
+class TestProposalVoteSummaryBzr(
+ TestProposalVoteSummaryMixin, BzrMixin, TestCaseWithFactory):
+ """Test the vote summary for Bazaar."""
+
+
+class TestProposalVoteSummaryGit(
+ TestProposalVoteSummaryMixin, GitMixin, TestCaseWithFactory):
+ """Test the vote summary for Git."""
+
+
class TestMerges(BrowserTestCase):
layer = DatabaseFunctionalLayer
@@ -193,7 +259,7 @@
package = self.factory.makeDistributionSourcePackage()
self.getViewBrowser(package, '+merges', rootsite='code')
- def test_query_count(self):
+ def test_query_count_bzr(self):
product = self.factory.makeProduct()
target = self.factory.makeBranch(
product=product, information_type=InformationType.USERDATA)
@@ -209,24 +275,51 @@
product, '+merges', rootsite='code', user=product.owner)
self.assertThat(recorder, HasQueryCount(Equals(41)))
- def test_productseries(self):
+ def test_query_count_git(self):
+ self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
+ product = self.factory.makeProduct()
+ [target] = self.factory.makeGitRefs(
+ target=product, information_type=InformationType.USERDATA)
+ for i in range(7):
+ [source] = self.factory.makeGitRefs(
+ target=product, information_type=InformationType.USERDATA)
+ self.factory.makeBranchMergeProposalForGit(
+ source_ref=source, target_ref=target)
+ flush_database_caches()
+ with StormStatementRecorder() as recorder:
+ self.getViewBrowser(
+ product, '+merges', rootsite='code', user=product.owner)
+ self.assertThat(recorder, HasQueryCount(Equals(38)))
+
+ def test_productseries_bzr(self):
target = self.factory.makeBranch()
- unique_name = target.unique_name
with person_logged_in(target.product.owner):
target.product.development_focus.branch = target
+ identity = target.identity
self.factory.makeBranchMergeProposal(target_branch=target)
view = self.getViewBrowser(target, '+merges', rootsite='code')
- self.assertIn(unique_name, view.contents)
-
-
-class ActiveReviewGroupsTest(TestCaseWithFactory):
- """Tests for groupings used in for active reviews."""
+ self.assertIn(identity, view.contents)
+
+ def test_product_git(self):
+ self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
+ [target] = self.factory.makeGitRefs()
+ with person_logged_in(target.target.owner):
+ getUtility(IGitRepositorySet).setDefaultRepository(
+ target.target, target.repository)
+ identity = target.identity
+ self.factory.makeBranchMergeProposalForGit(target_ref=target)
+ view = self.getViewBrowser(target, '+merges', rootsite='code')
+ self.assertIn(identity, view.contents)
+
+
+class ActiveReviewGroupsTestMixin:
+ """Tests for groupings used for active reviews."""
layer = DatabaseFunctionalLayer
def setUp(self):
- super(ActiveReviewGroupsTest, self).setUp()
- self.bmp = self.factory.makeBranchMergeProposal(
+ super(ActiveReviewGroupsTestMixin, self).setUp()
+ self.bmp = self._makeBranchMergeProposal(
set_state=BranchMergeProposalStatus.NEEDS_REVIEW)
def assertReviewGroupForReviewer(self, reviewer, group):
@@ -247,21 +340,21 @@
def test_approved(self):
# If the proposal is approved, then the group is approved.
- self.bmp = self.factory.makeBranchMergeProposal(
+ self.bmp = self._makeBranchMergeProposal(
set_state=BranchMergeProposalStatus.CODE_APPROVED)
self.assertReviewGroupForReviewer(None, ActiveReviewsView.APPROVED)
def test_work_in_progress(self):
# If the proposal is in progress, then the group is wip.
- self.bmp = self.factory.makeBranchMergeProposal(
+ self.bmp = self._makeBranchMergeProposal(
set_state=BranchMergeProposalStatus.WORK_IN_PROGRESS)
self.assertReviewGroupForReviewer(None, ActiveReviewsView.WIP)
- def test_source_branch_owner(self):
- # If the reviewer is the owner of the source branch, then the review
+ def test_merge_source_owner(self):
+ # If the reviewer is the owner of the merge source, then the review
# is MINE. This occurs whether or not the user is the logged in or
# not.
- reviewer = self.bmp.source_branch.owner
+ reviewer = self.bmp.merge_source.owner
self.assertReviewGroupForReviewer(reviewer, ActiveReviewsView.MINE)
def test_proposal_registrant(self):
@@ -271,13 +364,17 @@
self.assertReviewGroupForReviewer(reviewer, ActiveReviewsView.OTHER)
team = self.factory.makeTeam(self.bmp.registrant)
- removeSecurityProxy(self.bmp.source_branch).owner = team
+ naked_merge_source = removeSecurityProxy(self.bmp.merge_source)
+ if IGitRef.providedBy(naked_merge_source):
+ naked_merge_source.repository.owner = team
+ else:
+ naked_merge_source.owner = team
self.assertReviewGroupForReviewer(reviewer, ActiveReviewsView.MINE)
- def test_target_branch_owner(self):
- # For the target branch owner, it is to_do since they are the default
+ def test_merge_target_owner(self):
+ # For the merge target owner, it is to_do since they are the default
# reviewer.
- reviewer = self.bmp.target_branch.owner
+ reviewer = self.bmp.merge_target.owner
self.assertReviewGroupForReviewer(reviewer, ActiveReviewsView.TO_DO)
def test_group_pending_review(self):
@@ -299,7 +396,7 @@
def test_review_done(self):
# If the logged in user has a completed review, then the review is
# ARE_DOING.
- reviewer = self.bmp.target_branch.owner
+ reviewer = self.bmp.merge_target.owner
login_person(reviewer)
self.bmp.createComment(
reviewer, 'subject', vote=CodeReviewVote.APPROVE)
@@ -307,7 +404,17 @@
reviewer, ActiveReviewsView.ARE_DOING)
-class TestBranchMergeProposalListingItem(TestCaseWithFactory):
+class ActiveReviewGroupsTestBzr(
+ ActiveReviewGroupsTestMixin, BzrMixin, TestCaseWithFactory):
+ """Tests for groupings used for active reviews for Bazaar."""
+
+
+class ActiveReviewGroupsTestGit(
+ ActiveReviewGroupsTestMixin, GitMixin, TestCaseWithFactory):
+ """Tests for groupings used for active reviews for Git."""
+
+
+class TestBranchMergeProposalListingItemMixin:
"""Tests specifically relating to the BranchMergeProposalListingItem."""
layer = DatabaseFunctionalLayer
@@ -358,7 +465,17 @@
self.assertEqual(bmp.date_created, item.sort_key)
-class ActiveReviewSortingTest(TestCaseWithFactory):
+class TestBranchMergeProposalListingItemBzr(
+ TestBranchMergeProposalListingItemMixin, BzrMixin, TestCaseWithFactory):
+ """Test BranchMergeProposalListingItem for Bazaar."""
+
+
+class TestBranchMergeProposalListingItemGit(
+ TestBranchMergeProposalListingItemMixin, GitMixin, TestCaseWithFactory):
+ """Test BranchMergeProposalListingItem for Git."""
+
+
+class ActiveReviewSortingTestMixin:
"""Test the sorting of the active review groups."""
layer = DatabaseFunctionalLayer
@@ -366,14 +483,14 @@
def test_oldest_first(self):
# The oldest requested reviews should be first.
product = self.factory.makeProduct()
- bmp1 = self.factory.makeBranchMergeProposal(product=product)
- login_person(bmp1.source_branch.owner)
+ bmp1 = self._makeBranchMergeProposal(target=product)
+ login_person(bmp1.merge_source.owner)
bmp1.requestReview(datetime(2009, 6, 1, tzinfo=pytz.UTC))
- bmp2 = self.factory.makeBranchMergeProposal(product=product)
- login_person(bmp2.source_branch.owner)
+ bmp2 = self._makeBranchMergeProposal(target=product)
+ login_person(bmp2.merge_source.owner)
bmp2.requestReview(datetime(2009, 3, 1, tzinfo=pytz.UTC))
- bmp3 = self.factory.makeBranchMergeProposal(product=product)
- login_person(bmp3.source_branch.owner)
+ bmp3 = self._makeBranchMergeProposal(target=product)
+ login_person(bmp3.merge_source.owner)
bmp3.requestReview(datetime(2009, 1, 1, tzinfo=pytz.UTC))
login(ANONYMOUS)
view = create_initialized_view(
@@ -383,8 +500,18 @@
[item.context for item in view.review_groups[view.OTHER]])
-class ActiveReviewsWithPrivateBranches(TestCaseWithFactory):
- """Test the sorting of the active review groups."""
+class ActiveReviewSortingTestBzr(
+ ActiveReviewSortingTestMixin, BzrMixin, TestCaseWithFactory):
+ """Test the sorting of the active review groups for Bazaar."""
+
+
+class ActiveReviewSortingTestGit(
+ ActiveReviewSortingTestMixin, GitMixin, TestCaseWithFactory):
+ """Test the sorting of the active review groups for Git."""
+
+
+class ActiveReviewsWithPrivateBranchesMixin:
+ """Test reviews of private branches."""
layer = DatabaseFunctionalLayer
@@ -392,32 +519,42 @@
# Merge proposals against private branches are visible to
# the branch owner.
product = self.factory.makeProduct()
- branch = self.factory.makeBranch(
- product=product, information_type=InformationType.USERDATA)
+ branch = self._makeBranch(
+ target=product, information_type=InformationType.USERDATA)
with person_logged_in(removeSecurityProxy(branch).owner):
- mp = self.factory.makeBranchMergeProposal(target_branch=branch)
+ mp = self._makeBranchMergeProposal(merge_target=branch)
view = create_initialized_view(
branch, name='+activereviews', rootsite='code')
self.assertEqual([mp], list(view.getProposals()))
-class PersonActiveReviewsPerformance(TestCaseWithFactory):
+class ActiveReviewsWithPrivateBranchesBzr(
+ ActiveReviewsWithPrivateBranchesMixin, BzrMixin, TestCaseWithFactory):
+ """Test reviews of private Bazaar branches."""
+
+
+class ActiveReviewsWithPrivateBranchesGit(
+ ActiveReviewsWithPrivateBranchesMixin, GitMixin, TestCaseWithFactory):
+ """Test reviews of references in private Git repositories."""
+
+
+class PersonActiveReviewsPerformanceMixin:
"""Test the performance of the person's active reviews page."""
layer = LaunchpadFunctionalLayer
def setupBMP(self, bmp):
self.factory.makePreviewDiff(merge_proposal=bmp)
- login_person(bmp.source_branch.owner)
+ login_person(bmp.merge_source.owner)
bmp.requestReview()
- def createUserBMP(self, reviewer=None, target_branch_owner=None):
- target_branch = None
- if target_branch_owner is not None:
- target_branch = self.factory.makePackageBranch(
- owner=target_branch_owner)
- bmp = self.factory.makeBranchMergeProposal(
- reviewer=reviewer, target_branch=target_branch)
+ def createUserBMP(self, reviewer=None, merge_target_owner=None):
+ merge_target = None
+ if merge_target_owner is not None:
+ merge_target = self._makePackageBranch(
+ owner=merge_target_owner)
+ bmp = self._makeBranchMergeProposal(
+ reviewer=reviewer, merge_target=merge_target)
self.setupBMP(bmp)
return bmp
@@ -431,10 +568,9 @@
# Create one of the two types of BMP which will be displayed
# on a person's +activereviews page:
# - A BMP for which the person is the reviewer.
- # - A BMP for which the person is the owner of the target
- # branch.
+ # - A BMP for which the person is the owner of the merge target.
if i % 2 == 0:
- self.createUserBMP(target_branch_owner=user)
+ self.createUserBMP(merge_target_owner=user)
else:
self.createUserBMP(reviewer=user)
login_person(user)
@@ -457,9 +593,9 @@
self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
def createProductBMP(self, product):
- target_branch = self.factory.makeStackedOnBranchChain(product=product)
- bmp = self.factory.makeBranchMergeProposal(
- product=product, target_branch=target_branch)
+ merge_target = self._makeStackedOnBranchChain(target=product)
+ bmp = self._makeBranchMergeProposal(
+ target=product, merge_target=merge_target)
self.setupBMP(bmp)
return bmp
@@ -491,3 +627,13 @@
base_bmps + added_bmps)
self.assertEqual(base_bmps + added_bmps, view2.proposal_count)
self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
+
+
+class PersonActiveReviewsPerformanceBzr(
+ PersonActiveReviewsPerformanceMixin, BzrMixin, TestCaseWithFactory):
+ """Test the performance of the person's active reviews page for Bazaar."""
+
+
+class PersonActiveReviewsPerformanceGit(
+ PersonActiveReviewsPerformanceMixin, GitMixin, TestCaseWithFactory):
+ """Test the performance of the person's active reviews page for Git."""
=== modified file 'lib/lp/code/interfaces/gitref.py'
--- lib/lp/code/interfaces/gitref.py 2015-04-24 12:58:46 +0000
+++ lib/lp/code/interfaces/gitref.py 2015-05-12 17:28:52 +0000
@@ -43,11 +43,12 @@
BranchMergeProposalStatus,
GitObjectType,
)
+from lp.code.interfaces.hasbranches import IHasMergeProposals
from lp.registry.interfaces.person import IPerson
from lp.services.webapp.interfaces import ITableBatchNavigator
-class IGitRef(Interface):
+class IGitRef(IHasMergeProposals):
"""A reference in a Git repository."""
# XXX cjwatson 2015-01-19 bug=760849: "beta" is a lie to get WADL
=== modified file 'lib/lp/code/interfaces/hasbranches.py'
--- lib/lp/code/interfaces/hasbranches.py 2013-01-07 02:40:55 +0000
+++ lib/lp/code/interfaces/hasbranches.py 2015-05-12 17:28:52 +0000
@@ -130,6 +130,21 @@
:returns: A list of `IBranchMergeProposal`.
"""
+ def getOwnedAndRequestedReviews(status=None, visible_by_user=None,
+ project=None):
+ """Returns merge proposals for branches owned by a person, or where
+ that person was asked to review.
+
+ This does not include merge proposals that were requested from
+ teams that the person is part of. If status is not passed then
+ it will return proposals that are in the "Needs Review" state.
+
+ :param status: A list of statuses to filter with.
+ :param visible_by_user: Normally the user who is asking.
+ :param project: Limit results to branches for this `IProduct`.
+ :returns: A list of `IBranchMergeProposal`.
+ """
+
class IHasCodeImports(Interface):
"""Some things can have code imports that target them.
=== modified file 'lib/lp/code/model/branchmergeproposal.py'
--- lib/lp/code/model/branchmergeproposal.py 2015-04-22 16:42:57 +0000
+++ lib/lp/code/model/branchmergeproposal.py 2015-05-12 17:28:52 +0000
@@ -92,7 +92,10 @@
from lp.registry.interfaces.product import IProduct
from lp.registry.model.person import Person
from lp.services.config import config
-from lp.services.database.bulk import load_related
+from lp.services.database.bulk import (
+ load,
+ load_related,
+ )
from lp.services.database.constants import (
DEFAULT,
UTC_NOW,
@@ -1055,18 +1058,29 @@
from lp.code.model.branch import Branch
from lp.code.model.branchcollection import GenericBranchCollection
from lp.code.model.gitcollection import GenericGitCollection
+ from lp.code.model.gitref import GitRef
from lp.code.model.gitrepository import GitRepository
ids = set()
source_branch_ids = set()
- source_git_repository_ids = set()
+ git_repository_ids = set()
person_ids = set()
+ git_ref_keys = set()
for mp in branch_merge_proposals:
ids.add(mp.id)
if mp.source_branchID is not None:
source_branch_ids.add(mp.source_branchID)
if mp.source_git_repositoryID is not None:
- source_git_repository_ids.add(mp.source_git_repositoryID)
+ git_repository_ids.add(mp.source_git_repositoryID)
+ git_repository_ids.add(mp.target_git_repositoryID)
+ git_ref_keys.add(
+ (mp.source_git_repositoryID, mp.source_git_path))
+ git_ref_keys.add(
+ (mp.target_git_repositoryID, mp.target_git_path))
+ if mp.prerequisite_git_repositoryID is not None:
+ git_ref_keys.add(
+ (mp.prerequisite_git_repositoryID,
+ mp.prerequisite_git_path))
person_ids.add(mp.registrantID)
person_ids.add(mp.merge_reporterID)
@@ -1078,9 +1092,11 @@
GitRepository, branch_merge_proposals, (
"target_git_repositoryID", "prerequisite_git_repositoryID",
"source_git_repositoryID"))
+ load(GitRef, git_ref_keys)
# The stacked on branches are used to check branch visibility.
GenericBranchCollection.preloadVisibleStackedOnBranches(
branches, user)
+ GenericGitCollection.preloadVisibleRepositories(repositories, user)
if len(branches) == 0 and len(repositories) == 0:
return
@@ -1098,21 +1114,24 @@
cache.preview_diff = previewdiff
# Add source branch/repository owners' to the list of pre-loaded
- # persons.
+ # persons. We need the target repository owner as well; unlike
+ # branches, repository unique names aren't trigger-maintained.
person_ids.update(
branch.ownerID for branch in branches
if branch.id in source_branch_ids)
person_ids.update(
repository.owner_id for repository in repositories
- if repository.id in source_git_repository_ids)
+ if repository.id in git_repository_ids)
# Pre-load Person and ValidPersonCache.
list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
person_ids, need_validity=True))
# Pre-load branches'/repositories' data.
- GenericBranchCollection.preloadDataForBranches(branches)
- GenericGitCollection.preloadDataForRepositories(repositories)
+ if branches:
+ GenericBranchCollection.preloadDataForBranches(branches)
+ if repositories:
+ GenericGitCollection.preloadDataForRepositories(repositories)
class BranchMergeProposalGetter:
=== modified file 'lib/lp/code/model/gitcollection.py'
--- lib/lp/code/model/gitcollection.py 2015-04-22 16:42:57 +0000
+++ lib/lp/code/model/gitcollection.py 2015-05-12 17:28:52 +0000
@@ -9,6 +9,7 @@
]
from functools import partial
+from operator import attrgetter
from lazr.uri import (
InvalidURIError,
@@ -178,6 +179,20 @@
return []
@staticmethod
+ def preloadVisibleRepositories(repositories, user=None):
+ """Preload visibility for the given list of repositories."""
+ if len(repositories) == 0:
+ return
+ expressions = [
+ GitRepository.id.is_in(map(attrgetter("id"), repositories))]
+ if user is None:
+ collection = AnonymousGitCollection(filter_expressions=expressions)
+ else:
+ collection = VisibleGitCollection(
+ user=user, filter_expressions=expressions)
+ return list(collection.getRepositories())
+
+ @staticmethod
def preloadDataForRepositories(repositories):
"""Preload repositories' cached associated targets."""
load_related(Distribution, repositories, ['distribution_id'])
=== modified file 'lib/lp/code/model/hasbranches.py'
--- lib/lp/code/model/hasbranches.py 2013-03-04 04:17:17 +0000
+++ lib/lp/code/model/hasbranches.py 2015-05-12 17:28:52 +0000
@@ -11,7 +11,10 @@
'HasRequestedReviewsMixin',
]
+from functools import partial
+
from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
from lp.code.enums import BranchMergeProposalStatus
from lp.code.interfaces.branch import DEFAULT_BRANCH_STATUS_IN_LISTING
@@ -20,6 +23,11 @@
IBranchCollection,
)
from lp.code.interfaces.branchtarget import IBranchTarget
+from lp.code.interfaces.gitcollection import (
+ IAllGitRepositories,
+ IGitCollection,
+ )
+from lp.services.database.decoratedresultset import DecoratedResultSet
class HasBranchesMixin:
@@ -44,14 +52,28 @@
def getMergeProposals(self, status=None, visible_by_user=None,
eager_load=False):
"""See `IHasMergeProposals`."""
+ # Circular import.
+ from lp.code.model.branchmergeproposal import BranchMergeProposal
+
if not status:
status = (
BranchMergeProposalStatus.CODE_APPROVED,
BranchMergeProposalStatus.NEEDS_REVIEW,
BranchMergeProposalStatus.WORK_IN_PROGRESS)
- collection = IBranchCollection(self).visibleByUser(visible_by_user)
- return collection.getMergeProposals(status, eager_load=eager_load)
+ def _getProposals(interface):
+ collection = removeSecurityProxy(interface(self))
+ collection = collection.visibleByUser(visible_by_user)
+ return collection.getMergeProposals(status, eager_load=False)
+
+ proposals = _getProposals(IBranchCollection).union(
+ _getProposals(IGitCollection))
+ if not eager_load:
+ return proposals
+ else:
+ loader = partial(
+ BranchMergeProposal.preloadDataForBMPs, user=visible_by_user)
+ return DecoratedResultSet(proposals, pre_iter_hook=loader)
class HasRequestedReviewsMixin:
@@ -66,6 +88,34 @@
visible_by_user)
return visible_branches.getMergeProposalsForReviewer(self, status)
+ def getOwnedAndRequestedReviews(self, status=None, visible_by_user=None,
+ project=None, eager_load=False):
+ """See `IHasRequestedReviews`."""
+ # Circular import.
+ from lp.code.model.branchmergeproposal import BranchMergeProposal
+
+ if not status:
+ status = (BranchMergeProposalStatus.NEEDS_REVIEW,)
+
+ def _getProposals(collection):
+ collection = collection.visibleByUser(visible_by_user)
+ return collection.getMergeProposalsForPerson(
+ self, status, eager_load=False)
+
+ bzr_collection = removeSecurityProxy(getUtility(IAllBranches))
+ git_collection = removeSecurityProxy(getUtility(IAllGitRepositories))
+ if project is not None:
+ bzr_collection = bzr_collection.inProduct(project)
+ git_collection = git_collection.inProject(project)
+ proposals = _getProposals(bzr_collection).union(
+ _getProposals(git_collection))
+ if not eager_load:
+ return proposals
+ else:
+ loader = partial(
+ BranchMergeProposal.preloadDataForBMPs, user=visible_by_user)
+ return DecoratedResultSet(proposals, pre_iter_hook=loader)
+
class HasCodeImportsMixin:
=== modified file 'lib/lp/code/templates/branchmergeproposal-listing.pt'
--- lib/lp/code/templates/branchmergeproposal-listing.pt 2009-09-14 02:04:02 +0000
+++ lib/lp/code/templates/branchmergeproposal-listing.pt 2015-05-12 17:28:52 +0000
@@ -35,10 +35,10 @@
<td>
<a tal:attributes="href proposal/fmt:url">
<strong>
- <tal:source-branch replace="proposal/source_branch/bzr_identity"/>
+ <tal:merge-source replace="proposal/merge_source/identity"/>
</strong>
⇒
- <tal:source-branch replace="proposal/target_branch/bzr_identity"/>
+ <tal:merge-target replace="proposal/merge_target/identity"/>
</a>
</td>
<td tal:attributes="class string:mergestatus${proposal/queue_status/name}"
=== modified file 'lib/lp/code/templates/branchmergeproposal-macros.pt'
--- lib/lp/code/templates/branchmergeproposal-macros.pt 2010-01-10 21:40:41 +0000
+++ lib/lp/code/templates/branchmergeproposal-macros.pt 2015-05-12 17:28:52 +0000
@@ -45,10 +45,10 @@
<td>
<a tal:attributes="href proposal/fmt:url">
<strong>
- <tal:source-branch replace="proposal/source_branch/bzr_identity"/>
+ <tal:merge-source replace="proposal/merge_source/identity"/>
</strong>
⇒
- <tal:source-branch replace="proposal/target_branch/bzr_identity"/>
+ <tal:merge-target replace="proposal/merge_target/identity"/>
</a>
</td>
<td>
Follow ups