← Back to team overview

launchpad-reviewers team mailing list archive

[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">&#10060;</span>
+        <span tal:condition="status_result">&#9989;</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