changeset 6652:b60fb9461b18

Add JSON-RPC API for reading changeset status Allows reading a changeset status as json. This is useful for e.g. reporting, extracting review status for all changesets between two releases.
author Eivind Tagseth <eivindt@gmail.com>
date Mon, 13 Mar 2017 15:34:53 +0100
parents 986efdcfaf64
children 98d26beb0965
files docs/api/api.rst kallithea/controllers/api/api.py kallithea/model/db.py kallithea/tests/api/api_base.py kallithea/tests/api/test_api_git.py kallithea/tests/api/test_api_hg.py kallithea/tests/base.py kallithea/tests/fixture.py
diffstat 8 files changed, 164 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/docs/api/api.rst	Thu May 11 00:19:14 2017 +0200
+++ b/docs/api/api.rst	Mon Mar 13 15:34:53 2017 +0100
@@ -1007,6 +1007,75 @@
             }
     error:  null
 
+get_changeset
+^^^^^^^^^^^^^
+
+Get information and review status for a given changeset. This command can only
+be executed using the api_key of a user with read permissions to the
+repository.
+
+INPUT::
+
+    id : <id_for_response>
+    api_key : "<api_key>"
+    method  : "get_changeset"
+    args:     {
+                "repoid" : "<reponame or repo_id>",
+                "raw_id" : "<raw_id>",
+                "with_reviews": "<bool> = Optional(False)"
+              }
+
+OUTPUT::
+
+    id : <id_given_in_input>
+    result: {
+              "author":   "<full_author>",
+              "date":     "<date_time_of_commit>",
+              "message":  "<commit_message>",
+              "raw_id":   "<raw_id>",
+              "revision": "<numeric_revision>",
+              "short_id": "<short_id>",
+              "reviews": [{
+                    "reviewer":   "<username>",
+                    "modified_at": "<date_time_of_review>",  # iso 8601 date, server's timezone
+                    "status":   "<status_of_review>",        # "under_review", "approved" or "rejected"
+                 },
+                 ...
+              ]
+            }
+    error:  null
+
+Example output::
+
+    {
+      "id" : 1,
+      "error" : null,
+      "result" : {
+        "author" : {
+          "email" : "user@example.com",
+          "name" : "Kallithea Admin"
+        },
+        "changed" : [],
+        "short_id" : "e1022d3d28df",
+        "date" : "2017-03-28T09:09:03",
+        "added" : [
+          "README.rst"
+        ],
+        "removed" : [],
+        "revision" : 0,
+        "raw_id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
+        "message" : "Added file via Kallithea",
+        "id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
+        "reviews" : [
+          {
+            "status" : "under_review",
+            "modified_at" : "2017-03-28T09:17:08.618",
+            "reviewer" : "user"
+          }
+        ]
+      }
+    }
+
 
 API access for web views
 ------------------------
--- a/kallithea/controllers/api/api.py	Thu May 11 00:19:14 2017 +0200
+++ b/kallithea/controllers/api/api.py	Mon Mar 13 15:34:53 2017 +0100
@@ -47,12 +47,15 @@
 from kallithea.model.user import UserModel
 from kallithea.model.user_group import UserGroupModel
 from kallithea.model.gist import GistModel
+from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.model.db import (
     Repository, Setting, UserIpMap, Permission, User, Gist,
     RepoGroup, UserGroup)
 from kallithea.lib.compat import json
 from kallithea.lib.exceptions import (
     DefaultUserException, UserGroupsAssignedException)
+from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
+from kallithea.lib.vcs.backends.base import EmptyChangeset
 
 log = logging.getLogger(__name__)
 
@@ -2469,3 +2472,22 @@
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to delete gist ID:%s'
                                % (gist.gist_access_id,))
+
+    # permission check inside
+    def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
+        repo = get_repo_or_error(repoid)
+        if not HasRepoPermissionLevel('read')(repo.repo_name):
+            raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
+        changeset = repo.get_changeset(raw_id)
+        if isinstance(changeset, EmptyChangeset):
+            raise JSONRPCError('Changeset %s does not exist' % raw_id)
+
+        info = dict(changeset.as_dict())
+
+        with_reviews = Optional.extract(with_reviews)
+        if with_reviews:
+                reviews = ChangesetStatusModel().get_statuses(
+                                    repo.repo_name, raw_id)
+                info["reviews"] = reviews
+
+        return info
--- a/kallithea/model/db.py	Thu May 11 00:19:14 2017 +0200
+++ b/kallithea/model/db.py	Mon Mar 13 15:34:53 2017 +0100
@@ -2287,6 +2287,13 @@
     def status_lbl(self):
         return ChangesetStatus.get_status_lbl(self.status)
 
+    def __json__(self):
+        return dict(
+            status=self.status,
+            modified_at=self.modified_at,
+            reviewer=self.author.username,
+            )
+
 
 class PullRequest(Base, BaseDbModel):
     __tablename__ = 'pull_requests'
--- a/kallithea/tests/api/api_base.py	Thu May 11 00:19:14 2017 +0200
+++ b/kallithea/tests/api/api_base.py	Mon Mar 13 15:34:53 2017 +0100
@@ -2449,3 +2449,51 @@
         response = api_call(self, params)
         expected = Setting.get_server_info()
         self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_changeset(self):
+        review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
+        id_, params = _build_data(self.apikey, 'get_changeset',
+                                  repoid=self.REPO, raw_id = self.TEST_REVISION)
+        response = api_call(self, params)
+        result = json.loads(response.body)["result"]
+        assert result["raw_id"] == self.TEST_REVISION
+        assert not result.has_key("reviews")
+
+    def test_api_get_changeset_with_reviews(self):
+        reviewobjs = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
+        id_, params = _build_data(self.apikey, 'get_changeset',
+                                  repoid=self.REPO, raw_id = self.TEST_REVISION,
+                                  with_reviews = True)
+        response = api_call(self, params)
+        result = json.loads(response.body)["result"]
+        assert result["raw_id"] == self.TEST_REVISION
+        assert result.has_key("reviews")
+        assert len(result["reviews"]) == 1
+        review = result["reviews"][0]
+        expected = {
+            'status': 'approved',
+            'modified_at': reviewobjs[0].modified_at.isoformat()[:-3],
+            'reviewer': 'test_admin',
+        }
+        assert review == expected
+
+    def test_api_get_changeset_that_does_not_exist(self):
+        """ Fetch changeset status for non-existant changeset.
+        revision id is the above git hash used in the test above with the
+        last 3 nibbles replaced with 0xf.  Should not exist for git _or_ hg.
+        """
+        id_, params = _build_data(self.apikey, 'get_changeset',
+                                  repoid=self.REPO, raw_id = '7ab37bc680b4aa72c34d07b230c866c28e9fcfff')
+        response = api_call(self, params)
+        expected = u'Changeset %s does not exist' % ('7ab37bc680b4aa72c34d07b230c866c28e9fcfff',)
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_get_changeset_without_permission(self):
+        review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
+        RepoModel().revoke_user_permission(repo=self.REPO, user=self.TEST_USER_LOGIN)
+        RepoModel().revoke_user_permission(repo=self.REPO, user="default")
+        id_, params = _build_data(self.apikey_regular, 'get_changeset',
+                                  repoid=self.REPO, raw_id = self.TEST_REVISION)
+        response = api_call(self, params)
+        expected = u'Access denied to repo %s' % self.REPO
+        self._compare_error(id_, expected, given=response.body)
--- a/kallithea/tests/api/test_api_git.py	Thu May 11 00:19:14 2017 +0200
+++ b/kallithea/tests/api/test_api_git.py	Mon Mar 13 15:34:53 2017 +0100
@@ -12,10 +12,11 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from kallithea.tests.base import TestController, GIT_REPO
+from kallithea.tests.base import TestController, GIT_REPO, GIT_TEST_REVISION
 from kallithea.tests.api.api_base import _BaseTestApi
 
 
 class TestGitApi(_BaseTestApi, TestController):
     REPO = GIT_REPO
     REPO_TYPE = 'git'
+    TEST_REVISION = GIT_TEST_REVISION
--- a/kallithea/tests/api/test_api_hg.py	Thu May 11 00:19:14 2017 +0200
+++ b/kallithea/tests/api/test_api_hg.py	Mon Mar 13 15:34:53 2017 +0100
@@ -12,10 +12,11 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from kallithea.tests.base import TestController, HG_REPO
+from kallithea.tests.base import TestController, HG_REPO, HG_TEST_REVISION
 from kallithea.tests.api.api_base import _BaseTestApi
 
 
 class TestHgApi(_BaseTestApi, TestController):
     REPO = HG_REPO
     REPO_TYPE = 'hg'
+    TEST_REVISION = HG_TEST_REVISION
--- a/kallithea/tests/base.py	Thu May 11 00:19:14 2017 +0200
+++ b/kallithea/tests/base.py	Mon Mar 13 15:34:53 2017 +0100
@@ -48,7 +48,7 @@
     'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
     'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
     'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO',
-    'GIT_REMOTE_REPO', 'SCM_TESTS',
+    'GIT_REMOTE_REPO', 'SCM_TESTS', 'HG_TEST_REVISION', 'GIT_TEST_REVISION',
 ]
 
 # Invoke websetup with the current config file
@@ -80,6 +80,10 @@
 HG_FORK = u'vcs_test_hg_fork'
 GIT_FORK = u'vcs_test_git_fork'
 
+HG_TEST_REVISION = u"a53d9201d4bc278910d416d94941b7ea007ecd52"
+GIT_TEST_REVISION = u"7ab37bc680b4aa72c34d07b230c866c28e9fc204"
+
+
 ## VCS
 SCM_TESTS = ['hg', 'git']
 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
--- a/kallithea/tests/fixture.py	Thu May 11 00:19:14 2017 +0200
+++ b/kallithea/tests/fixture.py	Mon Mar 13 15:34:53 2017 +0100
@@ -22,7 +22,7 @@
 import tarfile
 from os.path import dirname
 
-from kallithea.model.db import Repository, User, RepoGroup, UserGroup, Gist
+from kallithea.model.db import Repository, User, RepoGroup, UserGroup, Gist, ChangesetStatus
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.user import UserModel
@@ -30,6 +30,8 @@
 from kallithea.model.user_group import UserGroupModel
 from kallithea.model.gist import GistModel
 from kallithea.model.scm import ScmModel
+from kallithea.model.comment import ChangesetCommentsModel
+from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.lib.db_manage import DbManage
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.tests.base import invalidate_all_caches, GIT_REPO, HG_REPO, TESTS_TMP_PATH, TEST_USER_ADMIN_LOGIN
@@ -309,6 +311,12 @@
             )
         return cs
 
+    def review_changeset(self, repo, revision, status, author=TEST_USER_ADMIN_LOGIN):
+        comment = ChangesetCommentsModel().create(u"review comment", repo, author, revision=revision, send_email=False)
+        csm = ChangesetStatusModel().set_status(repo, ChangesetStatus.STATUS_APPROVED, author, comment, revision=revision)
+        Session().commit()
+        return csm
+
 
 #==============================================================================
 # Global test environment setup