Merge lp:~twom/launchpad/git-active-review-on-more-git-repo into lp:launchpad
- git-active-review-on-more-git-repo
- Merge into devel
Proposed by
Tom Wardill
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 18763 | ||||
Proposed branch: | lp:~twom/launchpad/git-active-review-on-more-git-repo | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
301 lines (+165/-4) 8 files modified
lib/lp/_schema_circular_imports.py (+2/-0) lib/lp/code/browser/configure.zcml (+10/-0) lib/lp/code/browser/gitrepository.py (+29/-0) lib/lp/code/browser/tests/test_gitrepository.py (+49/-2) lib/lp/code/interfaces/gitrepository.py (+16/-0) lib/lp/code/model/gitrepository.py (+17/-2) lib/lp/code/templates/gitrepository-index.pt (+7/-0) lib/lp/code/templates/gitrepository-pending-merges.pt (+35/-0) |
||||
To merge this branch: | bzr merge lp:~twom/launchpad/git-active-review-on-more-git-repo | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+353884@code.launchpad.net |
Commit message
Add available review targets and proposals to the git repository overview page
Description of the change
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Needs Fixing
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/_schema_circular_imports.py' | |||
2 | --- lib/lp/_schema_circular_imports.py 2018-05-01 16:08:03 +0000 | |||
3 | +++ lib/lp/_schema_circular_imports.py 2018-08-29 17:12:28 +0000 | |||
4 | @@ -521,6 +521,8 @@ | |||
5 | 521 | IGitRepository, '_api_landing_candidates', IBranchMergeProposal) | 521 | IGitRepository, '_api_landing_candidates', IBranchMergeProposal) |
6 | 522 | patch_collection_property( | 522 | patch_collection_property( |
7 | 523 | IGitRepository, 'dependent_landings', IBranchMergeProposal) | 523 | IGitRepository, 'dependent_landings', IBranchMergeProposal) |
8 | 524 | patch_collection_return_type( | ||
9 | 525 | IGitRepository, 'getMergeProposals', IBranchMergeProposal) | ||
10 | 524 | 526 | ||
11 | 525 | # ILiveFSFile | 527 | # ILiveFSFile |
12 | 526 | patch_reference_property(ILiveFSFile, 'livefsbuild', ILiveFSBuild) | 528 | patch_reference_property(ILiveFSFile, 'livefsbuild', ILiveFSBuild) |
13 | 527 | 529 | ||
14 | === modified file 'lib/lp/code/browser/configure.zcml' | |||
15 | --- lib/lp/code/browser/configure.zcml 2018-08-20 23:33:01 +0000 | |||
16 | +++ lib/lp/code/browser/configure.zcml 2018-08-29 17:12:28 +0000 | |||
17 | @@ -819,6 +819,9 @@ | |||
18 | 819 | name="++repository-recipes" | 819 | name="++repository-recipes" |
19 | 820 | template="../templates/gitrepository-recipes.pt"/> | 820 | template="../templates/gitrepository-recipes.pt"/> |
20 | 821 | <browser:page | 821 | <browser:page |
21 | 822 | name="++repository-pending-merges" | ||
22 | 823 | template="../templates/gitrepository-pending-merges.pt"/> | ||
23 | 824 | <browser:page | ||
24 | 822 | name="++repository-import-details" | 825 | name="++repository-import-details" |
25 | 823 | template="../templates/import-details.pt"/> | 826 | template="../templates/import-details.pt"/> |
26 | 824 | </browser:pages> | 827 | </browser:pages> |
27 | @@ -902,6 +905,13 @@ | |||
28 | 902 | permission="launchpad.AnyAllowedPerson" | 905 | permission="launchpad.AnyAllowedPerson" |
29 | 903 | name="+try-again" | 906 | name="+try-again" |
30 | 904 | template="../templates/inline-form-only-buttons.pt"/> | 907 | template="../templates/inline-form-only-buttons.pt"/> |
31 | 908 | <browser:page | ||
32 | 909 | for="lp.code.interfaces.gitrepository.IGitRepository" | ||
33 | 910 | class="lp.code.browser.branchmergeproposallisting.BranchActiveReviewsView" | ||
34 | 911 | permission="zope.Public" | ||
35 | 912 | name="+activereviews" | ||
36 | 913 | template="../templates/active-reviews.pt"/> | ||
37 | 914 | |||
38 | 905 | <adapter | 915 | <adapter |
39 | 906 | provides="lp.services.webapp.interfaces.IBreadcrumb" | 916 | provides="lp.services.webapp.interfaces.IBreadcrumb" |
40 | 907 | for="lp.code.interfaces.gitrepository.IGitRepository" | 917 | for="lp.code.interfaces.gitrepository.IGitRepository" |
41 | 908 | 918 | ||
42 | === modified file 'lib/lp/code/browser/gitrepository.py' | |||
43 | --- lib/lp/code/browser/gitrepository.py 2018-08-24 16:52:27 +0000 | |||
44 | +++ lib/lp/code/browser/gitrepository.py 2018-08-29 17:12:28 +0000 | |||
45 | @@ -57,6 +57,9 @@ | |||
46 | 57 | from lp.app.vocabularies import InformationTypeVocabulary | 57 | from lp.app.vocabularies import InformationTypeVocabulary |
47 | 58 | from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription | 58 | from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription |
48 | 59 | from lp.code.browser.branch import CodeEditOwnerMixin | 59 | from lp.code.browser.branch import CodeEditOwnerMixin |
49 | 60 | from lp.code.browser.branchmergeproposal import ( | ||
50 | 61 | latest_proposals_for_each_branch, | ||
51 | 62 | ) | ||
52 | 60 | from lp.code.browser.codeimport import CodeImportTargetMixin | 63 | from lp.code.browser.codeimport import CodeImportTargetMixin |
53 | 61 | from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin | 64 | from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin |
54 | 62 | from lp.code.browser.widgets.gitrepositorytarget import ( | 65 | from lp.code.browser.widgets.gitrepositorytarget import ( |
55 | @@ -316,6 +319,7 @@ | |||
56 | 316 | return self.context.display_name | 319 | return self.context.display_name |
57 | 317 | 320 | ||
58 | 318 | label = page_title | 321 | label = page_title |
59 | 322 | show_merge_links = True | ||
60 | 319 | 323 | ||
61 | 320 | def initialize(self): | 324 | def initialize(self): |
62 | 321 | super(GitRepositoryView, self).initialize() | 325 | super(GitRepositoryView, self).initialize() |
63 | @@ -369,6 +373,31 @@ | |||
64 | 369 | """Is this an imported repository?""" | 373 | """Is this an imported repository?""" |
65 | 370 | return self.context.repository_type == GitRepositoryType.IMPORTED | 374 | return self.context.repository_type == GitRepositoryType.IMPORTED |
66 | 371 | 375 | ||
67 | 376 | @cachedproperty | ||
68 | 377 | def landing_candidates(self): | ||
69 | 378 | candidates = self.context.getPrecachedLandingCandidates(self.user) | ||
70 | 379 | return [proposal for proposal in candidates | ||
71 | 380 | if check_permission("launchpad.View", proposal)] | ||
72 | 381 | |||
73 | 382 | def _getBranchCountText(self, count): | ||
74 | 383 | """Help to show user friendly text.""" | ||
75 | 384 | if count == 0: | ||
76 | 385 | return 'No branches' | ||
77 | 386 | elif count == 1: | ||
78 | 387 | return '1 branch' | ||
79 | 388 | else: | ||
80 | 389 | return '%s branches' % count | ||
81 | 390 | |||
82 | 391 | @cachedproperty | ||
83 | 392 | def landing_candidate_count_text(self): | ||
84 | 393 | return self._getBranchCountText(len(self.landing_candidates)) | ||
85 | 394 | |||
86 | 395 | @cachedproperty | ||
87 | 396 | def landing_targets(self): | ||
88 | 397 | """Return a filtered list of landing targets.""" | ||
89 | 398 | targets = self.context.getPrecachedLandingTargets(self.user) | ||
90 | 399 | return latest_proposals_for_each_branch(targets) | ||
91 | 400 | |||
92 | 372 | 401 | ||
93 | 373 | class GitRepositoryEditFormView(LaunchpadEditFormView): | 402 | class GitRepositoryEditFormView(LaunchpadEditFormView): |
94 | 374 | """Base class for forms that edit a Git repository.""" | 403 | """Base class for forms that edit a Git repository.""" |
95 | 375 | 404 | ||
96 | === modified file 'lib/lp/code/browser/tests/test_gitrepository.py' | |||
97 | --- lib/lp/code/browser/tests/test_gitrepository.py 2018-08-24 16:52:27 +0000 | |||
98 | +++ lib/lp/code/browser/tests/test_gitrepository.py 2018-08-29 17:12:28 +0000 | |||
99 | @@ -24,10 +24,16 @@ | |||
100 | 24 | from lp.app.enums import InformationType | 24 | from lp.app.enums import InformationType |
101 | 25 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | 25 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
102 | 26 | from lp.app.interfaces.services import IService | 26 | from lp.app.interfaces.services import IService |
104 | 27 | from lp.code.enums import GitRepositoryType | 27 | from lp.code.enums import ( |
105 | 28 | BranchMergeProposalStatus, | ||
106 | 29 | GitRepositoryType, | ||
107 | 30 | ) | ||
108 | 28 | from lp.code.interfaces.revision import IRevisionSet | 31 | from lp.code.interfaces.revision import IRevisionSet |
109 | 29 | from lp.code.tests.helpers import GitHostingFixture | 32 | from lp.code.tests.helpers import GitHostingFixture |
111 | 30 | from lp.registry.enums import BranchSharingPolicy | 33 | from lp.registry.enums import ( |
112 | 34 | BranchSharingPolicy, | ||
113 | 35 | VCSType, | ||
114 | 36 | ) | ||
115 | 31 | from lp.registry.interfaces.accesspolicy import IAccessPolicySource | 37 | from lp.registry.interfaces.accesspolicy import IAccessPolicySource |
116 | 32 | from lp.registry.interfaces.person import PersonVisibility | 38 | from lp.registry.interfaces.person import PersonVisibility |
117 | 33 | from lp.services.beautifulsoup import BeautifulSoup | 39 | from lp.services.beautifulsoup import BeautifulSoup |
118 | @@ -246,6 +252,47 @@ | |||
119 | 246 | browser = self.getUserBrowser(url, user=user) | 252 | browser = self.getUserBrowser(url, user=user) |
120 | 247 | self.assertIn(project_name, browser.contents) | 253 | self.assertIn(project_name, browser.contents) |
121 | 248 | 254 | ||
122 | 255 | def test_view_with_active_reviews(self): | ||
123 | 256 | repository = self.factory.makeGitRepository() | ||
124 | 257 | git_refs = self.factory.makeGitRefs( | ||
125 | 258 | repository, | ||
126 | 259 | paths=["refs/heads/master", "refs/heads/1.0", "refs/tags/1.1"]) | ||
127 | 260 | self.factory.makeBranchMergeProposalForGit( | ||
128 | 261 | target_ref=git_refs[0], | ||
129 | 262 | set_state=BranchMergeProposalStatus.NEEDS_REVIEW) | ||
130 | 263 | with person_logged_in(repository.owner): | ||
131 | 264 | browser = self.getViewBrowser(repository) | ||
132 | 265 | self.assertIsNotNone( | ||
133 | 266 | find_tag_by_id(browser.contents, 'landing-candidates')) | ||
134 | 267 | |||
135 | 268 | def test_landing_candidate_count(self): | ||
136 | 269 | source_repository = self.factory.makeGitRepository() | ||
137 | 270 | view = create_initialized_view(source_repository, '+index') | ||
138 | 271 | |||
139 | 272 | self.assertEqual('No branches', view._getBranchCountText(0)) | ||
140 | 273 | self.assertEqual('1 branch', view._getBranchCountText(1)) | ||
141 | 274 | self.assertEqual('2 branches', view._getBranchCountText(2)) | ||
142 | 275 | |||
143 | 276 | def test_view_with_landing_targets(self): | ||
144 | 277 | product = self.factory.makeProduct(name="foo", vcs=VCSType.GIT) | ||
145 | 278 | target_repository = self.factory.makeGitRepository(target=product) | ||
146 | 279 | source_repository = self.factory.makeGitRepository(target=product) | ||
147 | 280 | target_git_refs = self.factory.makeGitRefs( | ||
148 | 281 | target_repository, | ||
149 | 282 | paths=["refs/heads/master", "refs/heads/1.0", "refs/tags/1.1"]) | ||
150 | 283 | source_git_refs = self.factory.makeGitRefs( | ||
151 | 284 | source_repository, | ||
152 | 285 | paths=["refs/heads/master"]) | ||
153 | 286 | self.factory.makeBranchMergeProposalForGit( | ||
154 | 287 | target_ref=target_git_refs[0], | ||
155 | 288 | source_ref=source_git_refs[0], | ||
156 | 289 | set_state=BranchMergeProposalStatus.NEEDS_REVIEW) | ||
157 | 290 | with person_logged_in(target_repository.owner): | ||
158 | 291 | browser = self.getViewBrowser( | ||
159 | 292 | source_repository, user=source_repository.owner) | ||
160 | 293 | self.assertIsNotNone( | ||
161 | 294 | find_tag_by_id(browser.contents, 'landing-targets')) | ||
162 | 295 | |||
163 | 249 | 296 | ||
164 | 250 | class TestGitRepositoryViewPrivateArtifacts(BrowserTestCase): | 297 | class TestGitRepositoryViewPrivateArtifacts(BrowserTestCase): |
165 | 251 | """Tests that Git repositories with private team artifacts can be viewed. | 298 | """Tests that Git repositories with private team artifacts can be viewed. |
166 | 252 | 299 | ||
167 | === modified file 'lib/lp/code/interfaces/gitrepository.py' | |||
168 | --- lib/lp/code/interfaces/gitrepository.py 2017-11-06 09:32:45 +0000 | |||
169 | +++ lib/lp/code/interfaces/gitrepository.py 2018-08-29 17:12:28 +0000 | |||
170 | @@ -58,6 +58,7 @@ | |||
171 | 58 | from lp.app.enums import InformationType | 58 | from lp.app.enums import InformationType |
172 | 59 | from lp.app.validators import LaunchpadValidationError | 59 | from lp.app.validators import LaunchpadValidationError |
173 | 60 | from lp.code.enums import ( | 60 | from lp.code.enums import ( |
174 | 61 | BranchMergeProposalStatus, | ||
175 | 61 | BranchSubscriptionDiffSize, | 62 | BranchSubscriptionDiffSize, |
176 | 62 | BranchSubscriptionNotificationLevel, | 63 | BranchSubscriptionNotificationLevel, |
177 | 63 | CodeReviewNotificationLevel, | 64 | CodeReviewNotificationLevel, |
178 | @@ -531,6 +532,21 @@ | |||
179 | 531 | Source and prerequisite repositories are preloaded. | 532 | Source and prerequisite repositories are preloaded. |
180 | 532 | """ | 533 | """ |
181 | 533 | 534 | ||
182 | 535 | @operation_parameters( | ||
183 | 536 | status=List( | ||
184 | 537 | title=_("A list of merge proposal statuses to filter by."), | ||
185 | 538 | value_type=Choice(vocabulary=BranchMergeProposalStatus)), | ||
186 | 539 | merged_revision_ids=List(TextLine( | ||
187 | 540 | title=_('The target revision ID of the merge.')))) | ||
188 | 541 | @call_with(visible_by_user=REQUEST_USER) | ||
189 | 542 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. | ||
190 | 543 | @operation_returns_collection_of(Interface) | ||
191 | 544 | @export_read_operation() | ||
192 | 545 | @operation_for_version("devel") | ||
193 | 546 | def getMergeProposals(status=None, visible_by_user=None, | ||
194 | 547 | merged_revision_ids=None, eager_load=False): | ||
195 | 548 | """Return matching BranchMergeProposals.""" | ||
196 | 549 | |||
197 | 534 | def getMergeProposalByID(id): | 550 | def getMergeProposalByID(id): |
198 | 535 | """Return this repository's merge proposal with this id, or None.""" | 551 | """Return this repository's merge proposal with this id, or None.""" |
199 | 536 | 552 | ||
200 | 537 | 553 | ||
201 | === modified file 'lib/lp/code/model/gitrepository.py' | |||
202 | --- lib/lp/code/model/gitrepository.py 2018-07-24 11:57:33 +0000 | |||
203 | +++ lib/lp/code/model/gitrepository.py 2018-08-29 17:12:28 +0000 | |||
204 | @@ -888,7 +888,7 @@ | |||
205 | 888 | BranchMergeProposal.source_git_repository == self) | 888 | BranchMergeProposal.source_git_repository == self) |
206 | 889 | 889 | ||
207 | 890 | def getPrecachedLandingTargets(self, user): | 890 | def getPrecachedLandingTargets(self, user): |
209 | 891 | """See `IGitRef`.""" | 891 | """See `IGitRepository`.""" |
210 | 892 | loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) | 892 | loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) |
211 | 893 | return DecoratedResultSet(self.landing_targets, pre_iter_hook=loader) | 893 | return DecoratedResultSet(self.landing_targets, pre_iter_hook=loader) |
212 | 894 | 894 | ||
213 | @@ -915,7 +915,7 @@ | |||
214 | 915 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) | 915 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) |
215 | 916 | 916 | ||
216 | 917 | def getPrecachedLandingCandidates(self, user): | 917 | def getPrecachedLandingCandidates(self, user): |
218 | 918 | """See `IGitRef`.""" | 918 | """See `IGitRepository`.""" |
219 | 919 | loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) | 919 | loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) |
220 | 920 | return DecoratedResultSet( | 920 | return DecoratedResultSet( |
221 | 921 | self.landing_candidates, pre_iter_hook=loader) | 921 | self.landing_candidates, pre_iter_hook=loader) |
222 | @@ -942,6 +942,21 @@ | |||
223 | 942 | Not(BranchMergeProposal.queue_status.is_in( | 942 | Not(BranchMergeProposal.queue_status.is_in( |
224 | 943 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) | 943 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) |
225 | 944 | 944 | ||
226 | 945 | def getMergeProposals(self, status=None, visible_by_user=None, | ||
227 | 946 | merged_revision_ids=None, eager_load=False): | ||
228 | 947 | """See `IGitRepository`.""" | ||
229 | 948 | if not status: | ||
230 | 949 | status = ( | ||
231 | 950 | BranchMergeProposalStatus.CODE_APPROVED, | ||
232 | 951 | BranchMergeProposalStatus.NEEDS_REVIEW, | ||
233 | 952 | BranchMergeProposalStatus.WORK_IN_PROGRESS) | ||
234 | 953 | |||
235 | 954 | collection = getUtility(IAllGitRepositories).visibleByUser( | ||
236 | 955 | visible_by_user) | ||
237 | 956 | return collection.getMergeProposals( | ||
238 | 957 | status, target_repository=self, | ||
239 | 958 | merged_revision_ids=merged_revision_ids, eager_load=eager_load) | ||
240 | 959 | |||
241 | 945 | def getMergeProposalByID(self, id): | 960 | def getMergeProposalByID(self, id): |
242 | 946 | """See `IGitRepository`.""" | 961 | """See `IGitRepository`.""" |
243 | 947 | return self.landing_targets.find(BranchMergeProposal.id == id).one() | 962 | return self.landing_targets.find(BranchMergeProposal.id == id).one() |
244 | 948 | 963 | ||
245 | === modified file 'lib/lp/code/templates/gitrepository-index.pt' | |||
246 | --- lib/lp/code/templates/gitrepository-index.pt 2018-01-26 12:01:02 +0000 | |||
247 | +++ lib/lp/code/templates/gitrepository-index.pt 2018-08-29 17:12:28 +0000 | |||
248 | @@ -44,6 +44,13 @@ | |||
249 | 44 | </div> | 44 | </div> |
250 | 45 | </div> | 45 | </div> |
251 | 46 | 46 | ||
252 | 47 | <div class="yui-g first"> | ||
253 | 48 | <div id="repository-relations" class="portlet"> | ||
254 | 49 | <tal:repository-pending-merges | ||
255 | 50 | replace="structure context/@@++repository-pending-merges" /> | ||
256 | 51 | </div> | ||
257 | 52 | </div> | ||
258 | 53 | |||
259 | 47 | <div class="yui-g"> | 54 | <div class="yui-g"> |
260 | 48 | <div id="repository-relations" class="portlet"> | 55 | <div id="repository-relations" class="portlet"> |
261 | 49 | <tal:repository-recipes | 56 | <tal:repository-recipes |
262 | 50 | 57 | ||
263 | === added file 'lib/lp/code/templates/gitrepository-pending-merges.pt' | |||
264 | --- lib/lp/code/templates/gitrepository-pending-merges.pt 1970-01-01 00:00:00 +0000 | |||
265 | +++ lib/lp/code/templates/gitrepository-pending-merges.pt 2018-08-29 17:12:28 +0000 | |||
266 | @@ -0,0 +1,35 @@ | |||
267 | 1 | <div | ||
268 | 2 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
269 | 3 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
270 | 4 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
271 | 5 | tal:define=" | ||
272 | 6 | context_menu view/context/menu:context; | ||
273 | 7 | features request/features" | ||
274 | 8 | tal:condition="view/show_merge_links"> | ||
275 | 9 | |||
276 | 10 | <h3>Proposed merges</h3> | ||
277 | 11 | <div id="merge-links" | ||
278 | 12 | class="actions"> | ||
279 | 13 | <div id="merge-summary"> | ||
280 | 14 | |||
281 | 15 | <div id="landing-candidates" | ||
282 | 16 | tal:condition="view/landing_candidates"> | ||
283 | 17 | <img src="/@@/merge-proposal-icon" /> | ||
284 | 18 | <a href="+activereviews" tal:content="structure view/landing_candidate_count_text"> | ||
285 | 19 | 1 branch | ||
286 | 20 | </a> | ||
287 | 21 | proposed for merging into this repository. | ||
288 | 22 | |||
289 | 23 | </div> | ||
290 | 24 | |||
291 | 25 | <div id="landing-targets" tal:condition="view/landing_targets"> | ||
292 | 26 | <tal:landing-candidates repeat="mergeproposal view/landing_targets"> | ||
293 | 27 | <tal:merge-fragment | ||
294 | 28 | tal:replace="structure mergeproposal/@@+summary-fragment"/> | ||
295 | 29 | </tal:landing-candidates> | ||
296 | 30 | </div> | ||
297 | 31 | |||
298 | 32 | </div> | ||
299 | 33 | </div> | ||
300 | 34 | |||
301 | 35 | </div> |