launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #27840
[Merge] ~ilasc/launchpad:ui-revision-status-reports into launchpad:master
Ioana Lasc has proposed merging ~ilasc/launchpad:ui-revision-status-reports into launchpad:master.
Commit message:
Display revision status reports
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~ilasc/launchpad/+git/launchpad/+merge/413141
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilasc/launchpad:ui-revision-status-reports into launchpad:master.
diff --git a/lib/lp/code/browser/branchmergeproposal.py b/lib/lp/code/browser/branchmergeproposal.py
index b7c157a..8d287e7 100644
--- a/lib/lp/code/browser/branchmergeproposal.py
+++ b/lib/lp/code/browser/branchmergeproposal.py
@@ -127,6 +127,7 @@ from lp.services.webapp import (
stepthrough,
)
from lp.services.webapp.authorization import check_permission
+from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.escaping import structured
from lp.services.webapp.interfaces import ILaunchBag
@@ -665,6 +666,9 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
self.request.response.addErrorNotification(six.text_type(e))
self.next_url = canonical_url(self.context)
+ def getStatusReportsBatchNav(self, reports):
+ return BatchNavigator(reports, self.request, size=3)
+
@property
def comment_location(self):
"""Location of page for commenting on this proposal."""
diff --git a/lib/lp/code/browser/gitref.py b/lib/lp/code/browser/gitref.py
index b28f1d9..6a2aaab 100644
--- a/lib/lp/code/browser/gitref.py
+++ b/lib/lp/code/browser/gitref.py
@@ -66,6 +66,7 @@ from lp.services.webapp import (
Link,
)
from lp.services.webapp.authorization import check_permission
+from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.escaping import structured
from lp.snappy.browser.hassnaps import (
HasSnapsMenuMixin,
@@ -275,6 +276,9 @@ class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin):
'<a href="+recipes">%s recipes</a> using this branch.',
count).escapedtext
+ def getStatusReportsBatchNav(self, reports):
+ return BatchNavigator(reports, self.request, size=3)
+
class GitRefRegisterMergeProposalSchema(Interface):
"""The schema to define the form for registering a new merge proposal."""
diff --git a/lib/lp/code/browser/tests/test_branchmergeproposal.py b/lib/lp/code/browser/tests/test_branchmergeproposal.py
index e722def..c92d46e 100644
--- a/lib/lp/code/browser/tests/test_branchmergeproposal.py
+++ b/lib/lp/code/browser/tests/test_branchmergeproposal.py
@@ -68,6 +68,7 @@ from lp.code.browser.gitref import GitRefRegisterMergeProposalView
from lp.code.enums import (
BranchMergeProposalStatus,
CodeReviewVote,
+ RevisionStatusResult,
)
from lp.code.errors import (
GitRepositoryScanFault,
@@ -1633,6 +1634,52 @@ class TestBranchMergeProposalView(TestCaseWithFactory):
content=description[:497] + '...'))
self.assertThat(browser.contents, HTMLContains(expected_meta))
+ def test_revisionStatusReports_display(self):
+ bmp = self.factory.makeBranchMergeProposalForGit()
+ sha1 = six.ensure_text(hashlib.sha1(b'0').hexdigest())
+ commit_date = datetime(2015, 1, 1, tzinfo=pytz.UTC)
+ report1 = self.factory.makeRevisionStatusReport(
+ user=bmp.source_git_repository.owner,
+ git_repository=bmp.source_git_repository,
+ title="CI", commit_sha1=sha1,
+ result_summary="120/120 tests passed",
+ url="https://foo.com",
+ result=RevisionStatusResult.SUCCEEDED)
+
+ self.useFixture(GitHostingFixture(log=[
+ {
+ 'sha1': sha1,
+ 'message': 'Sample message',
+ 'author': {
+ 'name': 'Example Person',
+ 'email': 'person@xxxxxxxxxxx',
+ 'time': int(seconds_since_epoch(commit_date)),
+ },
+ }
+ ]))
+
+ browser = self.getUserBrowser(canonical_url(bmp, rootsite='code'),
+ user=bmp.source_git_repository.owner)
+
+ reports_section = find_tags_by_class(browser.contents,
+ "status-reports-table")
+ with person_logged_in(bmp.source_git_repository.owner):
+ self.assertThat(
+ reports_section[0],
+ Tag(
+ "first report title", "td",
+ text=report1.title))
+ self.assertThat(
+ reports_section[0],
+ Tag(
+ "first report summary", "td",
+ text=report1.result_summary))
+ self.assertThat(
+ reports_section[0],
+ Tag(
+ "first report url", "td",
+ text=report1.url))
+
def test_unmerged_commits_from_deleted_git_ref(self):
# Even if the source Git ref has been deleted, we still know its tip
# SHA-1 and can ask the repository for its unmerged commits.
diff --git a/lib/lp/code/browser/tests/test_gitref.py b/lib/lp/code/browser/tests/test_gitref.py
index e8f5b25..bec47da 100644
--- a/lib/lp/code/browser/tests/test_gitref.py
+++ b/lib/lp/code/browser/tests/test_gitref.py
@@ -19,6 +19,7 @@ from testtools.matchers import (
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
+from lp.code.enums import RevisionStatusResult
from lp.code.errors import GitRepositoryScanFault
from lp.code.interfaces.gitjob import IGitRefScanJobSource
from lp.code.interfaces.gitrepository import IGitRepositorySet
@@ -170,6 +171,66 @@ class TestGitRefView(BrowserTestCase):
self.assertIn(
error_msg, extract_text(find_main_content(browser.contents)))
+ def test_revisionStatusReports(self):
+ repository = self.factory.makeGitRepository()
+ [ref] = self.factory.makeGitRefs(repository=repository,
+ paths=["refs/heads/branch"])
+ log = self.makeCommitLog()
+
+ # create 2 status reposts for 2 of the 5 commits available here
+ report1 = self.factory.makeRevisionStatusReport(
+ user=repository.owner, git_repository=repository,
+ title="CI", commit_sha1=log[0]["sha1"],
+ result_summary="120/120 tests passed",
+ url="https://foo1.com",
+ result=RevisionStatusResult.SUCCEEDED)
+
+ report2 = self.factory.makeRevisionStatusReport(
+ user=repository.owner, git_repository=repository,
+ title="Lint", commit_sha1=log[1]["sha1"],
+ result_summary="Invalid import in test_file.py",
+ url="https://foo2.com",
+ result=RevisionStatusResult.FAILED)
+
+ self.hosting_fixture.getLog.result = list(log)
+ self.scanRef(ref, log[-1])
+ view = create_initialized_view(ref, "+index",
+ principal=repository.owner)
+ with person_logged_in(repository.owner):
+ contents = view.render()
+ reports_section = find_tags_by_class(contents, "status-reports-table")
+ with person_logged_in(repository.owner):
+ self.assertThat(
+ reports_section[0],
+ soupmatchers.Tag(
+ "first report title", "td",
+ text=report1.title))
+ self.assertThat(
+ reports_section[0],
+ soupmatchers.Tag(
+ "first report summary", "td",
+ text=report1.result_summary))
+ self.assertThat(
+ reports_section[0],
+ soupmatchers.Tag(
+ "first report url", "td",
+ text=report1.url))
+ self.assertThat(
+ reports_section[1],
+ soupmatchers.Tag(
+ "second report title", "td",
+ text=report2.title))
+ self.assertThat(
+ reports_section[1],
+ soupmatchers.Tag(
+ "second report summary", "td",
+ text=report2.result_summary))
+ self.assertThat(
+ reports_section[1],
+ soupmatchers.Tag(
+ "second report url", "td",
+ text=report2.url))
+
def test_clone_instructions(self):
[ref] = self.factory.makeGitRefs(paths=["refs/heads/branch"])
username = ref.owner.name
diff --git a/lib/lp/code/interfaces/gitref.py b/lib/lp/code/interfaces/gitref.py
index a5782d2..3a31f26 100644
--- a/lib/lp/code/interfaces/gitref.py
+++ b/lib/lp/code/interfaces/gitref.py
@@ -149,6 +149,15 @@ class IGitRefView(IHasMergeProposals, IHasRecipes, IPrivacy, IInformationType):
def getCodebrowseUrlForRevision(commit):
"""Construct a browsing URL for this Git at the given commit"""
+ def getStatusReports(commit):
+ """Get status reports for this repository at the given commit"""
+
+ def getCommitStatus(sha1):
+ """Help to show red or green icon at the top of the commit."""
+
+ def getIndividualReportStatus(report):
+ """Help to show status for each report."""
+
information_type = Attribute(
"The type of information contained in the repository containing this "
"reference.")
diff --git a/lib/lp/code/model/gitref.py b/lib/lp/code/model/gitref.py
index 61a7526..3ed5aa6 100644
--- a/lib/lp/code/model/gitref.py
+++ b/lib/lp/code/model/gitref.py
@@ -43,6 +43,7 @@ from lp.code.enums import (
BranchMergeProposalStatus,
GitObjectType,
GitRepositoryType,
+ RevisionStatusResult,
)
from lp.code.errors import (
BranchMergeProposalExists,
@@ -179,6 +180,23 @@ class GitRefMixin:
"""See `IGitRef`."""
return self.repository.getCodebrowseUrlForRevision(commit)
+ def getStatusReports(self, commit):
+ return self.repository.getStatusReports(commit)
+
+ def getCommitStatus(self, sha1):
+ """Help to show red or green icon at the top of the commit."""
+ reports = self.repository.getStatusReports(sha1)
+ for report in reports:
+ if report.result != RevisionStatusResult.SUCCEEDED:
+ return False
+ return True
+
+ def getIndividualReportStatus(self, report):
+ """Help to show status for each report."""
+ if report.result == RevisionStatusResult.SUCCEEDED:
+ return True
+ return False
+
@property
def information_type(self):
"""See `IGitRef`."""
diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
index 43a005a..42654bc 100644
--- a/lib/lp/code/model/gitrepository.py
+++ b/lib/lp/code/model/gitrepository.py
@@ -390,7 +390,7 @@ class RevisionStatusReportSet:
return IStore(RevisionStatusReport).find(
RevisionStatusReport,
git_repository=repository,
- commit_sha1=commit_sha1)
+ commit_sha1=commit_sha1).order_by(RevisionStatusReport.title)
class RevisionStatusArtifact(StormBase):
diff --git a/lib/lp/code/templates/git-macros.pt b/lib/lp/code/templates/git-macros.pt
index d21375e..54f7439 100644
--- a/lib/lp/code/templates/git-macros.pt
+++ b/lib/lp/code/templates/git-macros.pt
@@ -216,7 +216,45 @@
<tal:commit-message
replace="structure commit_message/fmt:obfuscate-email/fmt:text-to-html" />
</dd>
-
+ <dd class="subordinate collapsible status-reports-table"
+ tal:define="
+ sha1 python:commit_info['sha1'];
+ status_result python:ref.getCommitStatus(sha1);
+ reports python:ref.getStatusReports(sha1)">
+ <div class="sprite treeCollapsed">
+ <span tal:condition="not:status_result">❌</span>
+ <span tal:condition="status_result">✅</span>
+ <span> Revision Status</span>
+ </div>
+ <tal:batch
+ define="batchnav python:view.getStatusReportsBatchNav(reports);
+ batch batchnav/currentBatch">
+ <table class="listing" tal:condition="batch">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Url</th>
+ <th>Summary</th>
+ <th>Passed</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tal:reports repeat="report batch">
+ <tr>
+ <td tal:content="report/title"/>
+ <td tal:content="report/url"/>
+ <td tal:content="report/result_summary"/>
+ <td tal:content="python: ref.getIndividualReportStatus(report)"/>
+ </tr>
+ </tal:reports>
+ </tbody>
+ </table>
+ <div class="lower-batch-nav">
+ <tal:navigation
+ replace="structure batchnav/@@+navigation-links-lower" />
+ </div>
+ </tal:batch>
+ </dd>
<div tal:define="merge_proposal python:commit_info.get('merge_proposal')"
tal:condition="merge_proposal">
<dd class="subordinate commit-comment"
Follow ups