← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ilasc/launchpad:add-get-status-artifacts-api into launchpad:master

 

Ioana Lasc has proposed merging ~ilasc/launchpad:add-get-status-artifacts-api into launchpad:master.

Commit message:
Add a get status artifacts endpoint

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ilasc/launchpad/+git/launchpad/+merge/414392
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilasc/launchpad:add-get-status-artifacts-api into launchpad:master.
diff --git a/lib/lp/code/interfaces/revisionstatus.py b/lib/lp/code/interfaces/revisionstatus.py
index 20c6a5a..852571c 100644
--- a/lib/lp/code/interfaces/revisionstatus.py
+++ b/lib/lp/code/interfaces/revisionstatus.py
@@ -16,6 +16,7 @@ import http.client
 
 from lazr.restful.declarations import (
     error_status,
+    export_read_operation,
     export_write_operation,
     exported,
     exported_as_webservice_entry,
@@ -78,6 +79,18 @@ class IRevisionStatusReportView(Interface):
     date_finished = exported(Datetime(
         title=_("When the report has finished.")), readonly=False)
 
+    @operation_parameters(
+        artifact_type=Choice(vocabulary=RevisionStatusArtifactType,
+                             required=False))
+    @scoped(AccessTokenScope.REPOSITORY_BUILD_STATUS.title)
+    @export_read_operation()
+    @operation_for_version("devel")
+    def getArtifactsURLs(artifact_type):
+        """Retrieves the list of URLs for artifacts that exist for this report.
+
+        :param artifact_type: The type of artifact for the report.
+        """
+
 
 class IRevisionStatusReportEditableAttributes(Interface):
     """`IRevisionStatusReport` attributes that can be edited.
@@ -235,6 +248,9 @@ class IRevisionStatusArtifactSet(Interface):
     def findByReport(report):
         """Returns the set of artifacts for a given report."""
 
+    def getArtifactDownloadUrls(report, clauses):
+        """Returns download URLs for all artifacts under a given report."""
+
 
 class IRevisionStatusArtifact(Interface):
     id = Int(title=_("ID"), required=True, readonly=True)
@@ -249,3 +265,9 @@ class IRevisionStatusArtifact(Interface):
     artifact_type = Choice(
         title=_('The type of artifact, only log for now.'),
         vocabulary=RevisionStatusArtifactType)
+
+    def downloadUrl():
+        """URL for downloading the artifact.
+
+        :return: A URL for downloading this artifact.
+        """
diff --git a/lib/lp/code/model/revisionstatus.py b/lib/lp/code/model/revisionstatus.py
index de9bfff..a4c3061 100644
--- a/lib/lp/code/model/revisionstatus.py
+++ b/lib/lp/code/model/revisionstatus.py
@@ -33,6 +33,7 @@ from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
 from lp.services.database.sqlbase import convert_storm_clause_to_string
 from lp.services.database.stormbase import StormBase
+from lp.services.librarian.browser import ProxiedLibraryFileAlias
 from lp.services.librarian.interfaces import ILibraryFileAliasSet
 
 
@@ -117,6 +118,20 @@ class RevisionStatusReport(StormBase):
         if result is not None:
             self.transitionToNewResult(result)
 
+    def getArtifactsURLs(self, artifact_type):
+        clauses = [
+            RevisionStatusArtifact.report == self,
+        ]
+        if artifact_type == RevisionStatusArtifactType.LOG:
+            clauses.extend([RevisionStatusArtifact.artifact_type ==
+                            RevisionStatusArtifactType.LOG, ])
+        elif artifact_type == RevisionStatusArtifactType.BINARY:
+            clauses.extend([RevisionStatusArtifact.artifact_type ==
+                            RevisionStatusArtifactType.BINARY, ])
+
+        return getUtility(
+            IRevisionStatusArtifactSet).getArtifactDownloadUrls(clauses)
+
 
 @implementer(IRevisionStatusReportSet)
 class RevisionStatusReportSet:
@@ -184,6 +199,14 @@ class RevisionStatusArtifact(StormBase):
         self.report = report
         self.artifact_type = artifact_type
 
+    def downloadUrl(self):
+        if self.library_file.restricted:
+            url = ProxiedLibraryFileAlias(
+                self.library_file, self.report).http_url
+        else:
+            url = self.library_file.http_url
+        return url
+
 
 @implementer(IRevisionStatusArtifactSet)
 class RevisionStatusArtifactSet:
@@ -204,3 +227,11 @@ class RevisionStatusArtifactSet:
         return IStore(RevisionStatusArtifact).find(
             RevisionStatusArtifact,
             RevisionStatusArtifact.report == report)
+
+    def getArtifactDownloadUrls(self, clauses):
+        artifacts = IStore(RevisionStatusArtifact).find(
+            RevisionStatusArtifact, *clauses)
+        urls = []
+        for artifact in list(artifacts):
+            urls.append(artifact.downloadUrl())
+        return urls
diff --git a/lib/lp/code/model/tests/test_revisionstatus.py b/lib/lp/code/model/tests/test_revisionstatus.py
index 6ce84c5..05367de 100644
--- a/lib/lp/code/model/tests/test_revisionstatus.py
+++ b/lib/lp/code/model/tests/test_revisionstatus.py
@@ -6,6 +6,7 @@
 import hashlib
 import io
 
+import requests
 from testtools.matchers import (
     AnyMatch,
     Equals,
@@ -40,10 +41,11 @@ from lp.testing.pages import webservice_for_person
 class TestRevisionStatusReport(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
-    def makeRevisionStatusArtifact(self, report):
+    def makeRevisionStatusArtifact(self, report, artifact_type=None):
         # We don't need to upload files to the librarian in this test suite.
         lfa = self.factory.makeLibraryFileAlias(db_only=True)
-        return self.factory.makeRevisionStatusArtifact(lfa=lfa, report=report)
+        return self.factory.makeRevisionStatusArtifact(
+            lfa=lfa, report=report, artifact_type=artifact_type)
 
     def test_owner_public(self):
         # The owner of a public repository can view and edit its reports and
@@ -239,3 +241,64 @@ class TestRevisionStatusReportWebservice(TestCaseWithFactory):
                 result_summary=Equals(initial_result_summary),
                 result=Equals(RevisionStatusResult.SUCCEEDED),
                 date_finished=GreaterThan(date_finished_before_update)))
+
+    def test_getArtifactsURLs(self):
+        report = self.factory.makeRevisionStatusReport()
+        artifact_log = self.factory.makeRevisionStatusArtifact(
+            report=report, artifact_type=RevisionStatusArtifactType.LOG,
+            content=b'log_data')
+        log_url = artifact_log.library_file.http_url
+        artifact_binary = self.factory.makeRevisionStatusArtifact(
+            report=report, artifact_type=RevisionStatusArtifactType.BINARY,
+            content=b'binary_data')
+        binary_url = artifact_binary.library_file.http_url
+        requester = report.creator
+        repository = report.git_repository
+        report_url = api_url(report)
+        webservice = self.getWebservice(requester, repository)
+        response = webservice.named_get(
+            report_url, "getArtifactsURLs", artifact_type="Log")
+        self.assertEqual(200, response.status)
+        with person_logged_in(requester):
+            self.assertIn(log_url, response.jsonBody())
+            self.assertNotIn(binary_url, response.jsonBody())
+            file = requests.get(response.jsonBody()[0])
+            self.assertEqual(b'log_data', file.content)
+        response = webservice.named_get(
+            report_url, "getArtifactsURLs", artifact_type="Binary")
+        self.assertEqual(200, response.status)
+        with person_logged_in(requester):
+            self.assertNotIn(log_url, response.jsonBody())
+            self.assertIn(binary_url, response.jsonBody())
+            file = requests.get(response.jsonBody()[0])
+            self.assertEqual(b'binary_data', file.content)
+        response = webservice.named_get(
+            report_url, "getArtifactsURLs")
+        self.assertEqual(200, response.status)
+        with person_logged_in(requester):
+            self.assertIn(log_url, response.jsonBody())
+            self.assertIn(binary_url, response.jsonBody())
+
+    def test_getArtifactsURLs_restricted(self):
+        requester = self.factory.makePerson()
+        with person_logged_in(requester):
+            kwargs = {"owner": requester}
+            kwargs["information_type"] = InformationType.USERDATA
+            repository = self.factory.makeGitRepository(**kwargs)
+            report = self.factory.makeRevisionStatusReport(
+                git_repository=repository)
+            report_url = api_url(report)
+            artifact = self.factory.makeRevisionStatusArtifact(
+                report=report, artifact_type=RevisionStatusArtifactType.LOG,
+                content=b'log_data', restricted=True)
+            log_url = 'http://code.launchpad.test/%s/+status/%s/+files/%s' % (
+                repository.unique_name, report.id,
+                artifact.library_file.filename)
+
+        webservice = self.getWebservice(requester, repository)
+        response = webservice.named_get(
+            report_url, "getArtifactsURLs", artifact_type="Log")
+        self.assertEqual(200, response.status)
+
+        with person_logged_in(requester):
+            self.assertIn(log_url, response.jsonBody())
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 1588aad..d2a9a4e 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -1871,13 +1871,16 @@ class BareLaunchpadObjectFactory(ObjectFactory):
             result_summary, result)
 
     def makeRevisionStatusArtifact(
-            self, lfa=None, report=None,
-            artifact_type=RevisionStatusArtifactType.LOG):
+            self, lfa=None, content=None, report=None,
+            artifact_type=None, restricted=False):
         """Create a new RevisionStatusArtifact."""
         if lfa is None:
-            lfa = self.makeLibraryFileAlias()
+            lfa = self.makeLibraryFileAlias(
+                content=content, restricted=restricted)
         if report is None:
             report = self.makeRevisionStatusReport()
+        if artifact_type is None:
+            artifact_type = RevisionStatusArtifactType.LOG
         return getUtility(IRevisionStatusArtifactSet).new(
             lfa, report, artifact_type)
 

Follow ups