##// END OF EJS Templates
api: add get_pullrequest and comment_pullrequest methods...
domruf -
r6595:6452215a default
parent child Browse files
Show More
@@ -1140,6 +1140,95 b' Example output::'
1140 }
1140 }
1141 }
1141 }
1142
1142
1143 get_pullrequest
1144 ^^^^^^^^^^^^^^^
1145
1146 Get information and review status for a given pull request. This command can only be executed
1147 using the api_key of a user with read permissions to the original repository.
1148
1149 INPUT::
1150
1151 id : <id_for_response>
1152 api_key : "<api_key>"
1153 method : "get_pullrequest"
1154 args: {
1155 "pullrequest_id" : "<pullrequest_id>",
1156 }
1157
1158 OUTPUT::
1159
1160 id : <id_given_in_input>
1161 result: {
1162 "status": "<pull_request_status>",
1163 "pull_request_id": <pull_request_id>,
1164 "description": "<pull_request_description>",
1165 "title": "<pull_request_title>",
1166 "url": "<pull_request_url>",
1167 "reviewers": [
1168 {
1169 "username": "<user_name>",
1170 },
1171 ...
1172 ],
1173 "org_repo_url": "<repo_url>",
1174 "org_ref_parts": [
1175 "<ref_type>",
1176 "<ref_name>",
1177 "<raw_id>"
1178 ],
1179 "other_ref_parts": [
1180 "<ref_type>",
1181 "<ref_name>",
1182 "<raw_id>"
1183 ],
1184 "comments": [
1185 {
1186 "username": "<user_name>",
1187 "text": "<comment text>",
1188 "comment_id": "<comment_id>",
1189 },
1190 ...
1191 ],
1192 "owner": "<username>",
1193 "statuses": [
1194 {
1195 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1196 "reviewer": "<user_name>",
1197 "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
1198 },
1199 ...
1200 ],
1201 "revisions": [
1202 "<raw_id>",
1203 ...
1204 ]
1205 },
1206 error: null
1207
1208 comment_pullrequest
1209 ^^^^^^^^^^^^^^^^^^^
1210
1211 Add comment, change status or close a given pull request. This command can only be executed
1212 using the api_key of a user with read permissions to the original repository.
1213
1214 INPUT::
1215
1216 id : <id_for_response>
1217 api_key : "<api_key>"
1218 method : "comment_pullrequest"
1219 args: {
1220 "pull_request_id": "<pull_request_id>",
1221 "comment_msg": Optional(''),
1222 "status": Optional(None), # "under_review", "approved" or "rejected"
1223 "close_pr": Optional(False)",
1224 }
1225
1226 OUTPUT::
1227
1228 id : <id_given_in_input>
1229 result: True
1230 error: null
1231
1143
1232
1144 API access for web views
1233 API access for web views
1145 ------------------------
1234 ------------------------
@@ -48,14 +48,17 b' from kallithea.model.user import UserMod'
48 from kallithea.model.user_group import UserGroupModel
48 from kallithea.model.user_group import UserGroupModel
49 from kallithea.model.gist import GistModel
49 from kallithea.model.gist import GistModel
50 from kallithea.model.changeset_status import ChangesetStatusModel
50 from kallithea.model.changeset_status import ChangesetStatusModel
51 from kallithea.model.comment import ChangesetCommentsModel
52 from kallithea.model.pull_request import PullRequestModel
51 from kallithea.model.db import (
53 from kallithea.model.db import (
52 Repository, Setting, UserIpMap, Permission, User, Gist,
54 Repository, Setting, UserIpMap, Permission, User, Gist,
53 RepoGroup, UserGroup)
55 RepoGroup, UserGroup, PullRequest, ChangesetStatus)
54 from kallithea.lib.compat import json
56 from kallithea.lib.compat import json
55 from kallithea.lib.exceptions import (
57 from kallithea.lib.exceptions import (
56 DefaultUserException, UserGroupsAssignedException)
58 DefaultUserException, UserGroupsAssignedException)
57 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
59 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
58 from kallithea.lib.vcs.backends.base import EmptyChangeset
60 from kallithea.lib.vcs.backends.base import EmptyChangeset
61 from kallithea.lib.utils import action_logger
59
62
60 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
61
64
@@ -2506,3 +2509,65 b' class ApiController(JSONRPCController):'
2506 info["reviews"] = reviews
2509 info["reviews"] = reviews
2507
2510
2508 return info
2511 return info
2512
2513 # permission check inside
2514 def get_pullrequest(self, pullrequest_id):
2515 """
2516 Get given pull request by id
2517 """
2518 pull_request = PullRequest.get(pullrequest_id)
2519 if pull_request is None:
2520 raise JSONRPCError('pull request `%s` does not exist' % (pullrequest_id,))
2521 if not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name):
2522 raise JSONRPCError('not allowed')
2523 return pull_request.get_api_data()
2524
2525 # permission check inside
2526 def comment_pullrequest(self, pull_request_id, comment_msg='', status=None, close_pr=False):
2527 """
2528 Add comment, close and change status of pull request.
2529 """
2530 apiuser = get_user_or_error(request.authuser.user_id)
2531 pull_request = PullRequest.get(pull_request_id)
2532 if pull_request is None:
2533 raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
2534 if (not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name)):
2535 raise JSONRPCError('No permission to add comment. User needs at least reading permissions'
2536 ' to the source repository.')
2537 owner = apiuser.user_id == pull_request.owner_id
2538 reviewer = apiuser.user_id in [reviewer.user_id for reviewer in pull_request.reviewers]
2539 if close_pr and not (apiuser.admin or owner):
2540 raise JSONRPCError('No permission to close pull request. User needs to be admin or owner.')
2541 if status and not (apiuser.admin or owner or reviewer):
2542 raise JSONRPCError('No permission to change pull request status. User needs to be admin, owner or reviewer.')
2543 if pull_request.is_closed():
2544 raise JSONRPCError('pull request is already closed')
2545
2546 comment = ChangesetCommentsModel().create(
2547 text=comment_msg,
2548 repo=pull_request.org_repo.repo_id,
2549 author=apiuser.user_id,
2550 pull_request=pull_request.pull_request_id,
2551 f_path=None,
2552 line_no=None,
2553 status_change=(ChangesetStatus.get_status_lbl(status)),
2554 closing_pr=close_pr
2555 )
2556 action_logger(apiuser,
2557 'user_commented_pull_request:%s' % pull_request_id,
2558 pull_request.org_repo, request.ip_addr)
2559 if status:
2560 ChangesetStatusModel().set_status(
2561 pull_request.org_repo_id,
2562 status,
2563 apiuser.user_id,
2564 comment,
2565 pull_request=pull_request_id
2566 )
2567 if close_pr:
2568 PullRequestModel().close_pull_request(pull_request_id)
2569 action_logger(apiuser,
2570 'user_closed_pull_request:%s' % pull_request_id,
2571 pull_request.org_repo, request.ip_addr)
2572 Session().commit()
2573 return True
@@ -1515,7 +1515,12 b' class Repository(Base, BaseDbModel):'
1515 return repo
1515 return repo
1516
1516
1517 def __json__(self):
1517 def __json__(self):
1518 return dict(landing_rev = self.landing_rev)
1518 return dict(
1519 repo_id=self.repo_id,
1520 repo_name=self.repo_name,
1521 landing_rev=self.landing_rev,
1522 )
1523
1519
1524
1520 class RepoGroup(Base, BaseDbModel):
1525 class RepoGroup(Base, BaseDbModel):
1521 __tablename__ = 'groups'
1526 __tablename__ = 'groups'
@@ -2238,6 +2243,13 b' class ChangesetComment(Base, BaseDbModel'
2238 elif self.pull_request_id is not None:
2243 elif self.pull_request_id is not None:
2239 return self.pull_request.url(anchor=anchor)
2244 return self.pull_request.url(anchor=anchor)
2240
2245
2246 def __json__(self):
2247 return dict(
2248 comment_id=self.comment_id,
2249 username=self.author.username,
2250 text=self.text,
2251 )
2252
2241 def deletable(self):
2253 def deletable(self):
2242 return self.created_on > datetime.datetime.now() - datetime.timedelta(minutes=5)
2254 return self.created_on > datetime.datetime.now() - datetime.timedelta(minutes=5)
2243
2255
@@ -2408,9 +2420,24 b' class PullRequest(Base, BaseDbModel):'
2408 '''Return the id of this pull request, nicely formatted for displaying'''
2420 '''Return the id of this pull request, nicely formatted for displaying'''
2409 return self.make_nice_id(self.pull_request_id)
2421 return self.make_nice_id(self.pull_request_id)
2410
2422
2423 def get_api_data(self):
2424 return self.__json__()
2425
2411 def __json__(self):
2426 def __json__(self):
2412 return dict(
2427 return dict(
2413 revisions=self.revisions
2428 pull_request_id=self.pull_request_id,
2429 url=self.url(),
2430 reviewers=self.reviewers,
2431 revisions=self.revisions,
2432 owner=self.owner.username,
2433 title=self.title,
2434 description=self.description,
2435 org_repo_url=self.org_repo.clone_url(),
2436 org_ref_parts=self.org_ref_parts,
2437 other_ref_parts=self.other_ref_parts,
2438 status=self.status,
2439 comments=self.comments,
2440 statuses=self.statuses,
2414 )
2441 )
2415
2442
2416 def url(self, **kwargs):
2443 def url(self, **kwargs):
@@ -2446,6 +2473,11 b' class PullRequestReviewer(Base, BaseDbMo'
2446 user = relationship('User')
2473 user = relationship('User')
2447 pull_request = relationship('PullRequest')
2474 pull_request = relationship('PullRequest')
2448
2475
2476 def __json__(self):
2477 return dict(
2478 username=self.user.username if self.user else None,
2479 )
2480
2449
2481
2450 class Notification(Base, BaseDbModel):
2482 class Notification(Base, BaseDbModel):
2451 __tablename__ = 'notifications'
2483 __tablename__ = 'notifications'
@@ -19,6 +19,7 b' Tests for the JSON-RPC web api.'
19 import os
19 import os
20 import random
20 import random
21 import mock
21 import mock
22 import re
22
23
23 from kallithea.tests.base import *
24 from kallithea.tests.base import *
24 from kallithea.tests.fixture import Fixture
25 from kallithea.tests.fixture import Fixture
@@ -31,7 +32,8 b' from kallithea.model.repo_group import R'
31 from kallithea.model.meta import Session
32 from kallithea.model.meta import Session
32 from kallithea.model.scm import ScmModel
33 from kallithea.model.scm import ScmModel
33 from kallithea.model.gist import GistModel
34 from kallithea.model.gist import GistModel
34 from kallithea.model.db import Repository, User, Setting, Ui
35 from kallithea.model.changeset_status import ChangesetStatusModel
36 from kallithea.model.db import Repository, User, Setting, Ui, PullRequest, ChangesetStatus
35 from kallithea.lib.utils2 import time_to_datetime
37 from kallithea.lib.utils2 import time_to_datetime
36
38
37
39
@@ -2506,3 +2508,89 b' class _BaseTestApi(object):'
2506 response = api_call(self, params)
2508 response = api_call(self, params)
2507 expected = u'Access denied to repo %s' % self.REPO
2509 expected = u'Access denied to repo %s' % self.REPO
2508 self._compare_error(id_, expected, given=response.body)
2510 self._compare_error(id_, expected, given=response.body)
2511
2512 def test_api_get_pullrequest(self):
2513 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, 'get test')
2514 random_id = random.randrange(1, 9999)
2515 params = json.dumps({
2516 "id": random_id,
2517 "api_key": self.apikey,
2518 "method": 'get_pullrequest',
2519 "args": {"pullrequest_id": pull_request_id},
2520 })
2521 response = api_call(self, params)
2522 pullrequest = PullRequest().get(pull_request_id)
2523 expected = {
2524 "status": "new",
2525 "pull_request_id": pull_request_id,
2526 "description": "No description",
2527 "url": "/%s/pull-request/%s/_/%s" % (self.REPO, pull_request_id, "stable"),
2528 "reviewers": [{"username": "test_regular"}],
2529 "org_repo_url": "http://localhost:80/%s" % self.REPO,
2530 "org_ref_parts": ["branch", "stable", self.TEST_PR_SRC],
2531 "other_ref_parts": ["branch", "default", self.TEST_PR_DST],
2532 "comments": [{"username": TEST_USER_ADMIN_LOGIN, "text": "",
2533 "comment_id": pullrequest.comments[0].comment_id}],
2534 "owner": TEST_USER_ADMIN_LOGIN,
2535 "statuses": [{"status": "under_review", "reviewer": TEST_USER_ADMIN_LOGIN, "modified_at": "2000-01-01T00:00:00.000"} for i in range(0, len(self.TEST_PR_REVISIONS))],
2536 "title": "get test",
2537 "revisions": self.TEST_PR_REVISIONS,
2538 }
2539 self._compare_ok(random_id, expected,
2540 given=re.sub("\d\d\d\d\-\d\d\-\d\dT\d\d\:\d\d\:\d\d\.\d\d\d",
2541 "2000-01-01T00:00:00.000", response.body))
2542
2543 def test_api_close_pullrequest(self):
2544 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, 'close test')
2545 random_id = random.randrange(1, 9999)
2546 params = json.dumps({
2547 "id": random_id,
2548 "api_key": self.apikey,
2549 "method": "comment_pullrequest",
2550 "args": {"pull_request_id": pull_request_id, "close_pr": True},
2551 })
2552 response = api_call(self, params)
2553 self._compare_ok(random_id, True, given=response.body)
2554 pullrequest = PullRequest().get(pull_request_id)
2555 assert pullrequest.comments[-1].text == ''
2556 assert pullrequest.status == PullRequest.STATUS_CLOSED
2557 assert pullrequest.is_closed() == True
2558
2559 def test_api_status_pullrequest(self):
2560 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, "status test")
2561
2562 random_id = random.randrange(1, 9999)
2563 params = json.dumps({
2564 "id": random_id,
2565 "api_key": User.get_by_username(TEST_USER_REGULAR2_LOGIN).api_key,
2566 "method": "comment_pullrequest",
2567 "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
2568 })
2569 response = api_call(self, params)
2570 pullrequest = PullRequest().get(pull_request_id)
2571 self._compare_error(random_id, "No permission to change pull request status. User needs to be admin, owner or reviewer.", given=response.body)
2572 assert ChangesetStatus.STATUS_UNDER_REVIEW == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
2573 params = json.dumps({
2574 "id": random_id,
2575 "api_key": User.get_by_username(TEST_USER_REGULAR_LOGIN).api_key,
2576 "method": "comment_pullrequest",
2577 "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
2578 })
2579 response = api_call(self, params)
2580 self._compare_ok(random_id, True, given=response.body)
2581 pullrequest = PullRequest().get(pull_request_id)
2582 assert ChangesetStatus.STATUS_APPROVED == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
2583
2584 def test_api_comment_pullrequest(self):
2585 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, "comment test")
2586 random_id = random.randrange(1, 9999)
2587 params = json.dumps({
2588 "id": random_id,
2589 "api_key": self.apikey,
2590 "method": "comment_pullrequest",
2591 "args": {"pull_request_id": pull_request_id, "comment_msg": "Looks good to me"},
2592 })
2593 response = api_call(self, params)
2594 self._compare_ok(random_id, True, given=response.body)
2595 pullrequest = PullRequest().get(pull_request_id)
2596 assert pullrequest.comments[-1].text == u'Looks good to me'
@@ -20,3 +20,9 b' class TestGitApi(_BaseTestApi, TestContr'
20 REPO = GIT_REPO
20 REPO = GIT_REPO
21 REPO_TYPE = 'git'
21 REPO_TYPE = 'git'
22 TEST_REVISION = GIT_TEST_REVISION
22 TEST_REVISION = GIT_TEST_REVISION
23 TEST_PR_SRC = u'c60f01b77c42dce653d6b1d3b04689862c261929'
24 TEST_PR_DST = u'10cddef6b794696066fb346434014f0a56810218'
25 TEST_PR_REVISIONS = [u'1bead5880d2dbe831762bf7fb439ba2919b75fdd',
26 u'9bcd3ecfc8832a8cd881c1c1bbe2d13ffa9d94c7',
27 u'283de4dfca8479875a1befb8d4059f3bbb725145',
28 u'c60f01b77c42dce653d6b1d3b04689862c261929']
@@ -20,3 +20,10 b' class TestHgApi(_BaseTestApi, TestContro'
20 REPO = HG_REPO
20 REPO = HG_REPO
21 REPO_TYPE = 'hg'
21 REPO_TYPE = 'hg'
22 TEST_REVISION = HG_TEST_REVISION
22 TEST_REVISION = HG_TEST_REVISION
23 TEST_PR_SRC = u'4f7e2131323e0749a740c0a56ab68ae9269c562a'
24 TEST_PR_DST = u'92831aebf2f8dd4879e897024b89d09af214df1c'
25 TEST_PR_REVISIONS = [u'720bbdb27665d6262b313e8a541b654d0cbd5b27',
26 u'f41649565a9e89919a588a163e717b4084f8a3b1',
27 u'94f45ed825a113e61af7e141f44ca578374abef0',
28 u'fef5bfe1dc17611d5fb59a7f6f95c55c3606f933',
29 u'4f7e2131323e0749a740c0a56ab68ae9269c562a']
@@ -22,6 +22,10 b' import shutil'
22 import tarfile
22 import tarfile
23 from os.path import dirname
23 from os.path import dirname
24
24
25 import mock
26 from tg import request
27 from tg.util.webtest import test_context
28
25 from kallithea.model.db import Repository, User, RepoGroup, UserGroup, Gist, ChangesetStatus
29 from kallithea.model.db import Repository, User, RepoGroup, UserGroup, Gist, ChangesetStatus
26 from kallithea.model.meta import Session
30 from kallithea.model.meta import Session
27 from kallithea.model.repo import RepoModel
31 from kallithea.model.repo import RepoModel
@@ -32,9 +36,13 b' from kallithea.model.gist import GistMod'
32 from kallithea.model.scm import ScmModel
36 from kallithea.model.scm import ScmModel
33 from kallithea.model.comment import ChangesetCommentsModel
37 from kallithea.model.comment import ChangesetCommentsModel
34 from kallithea.model.changeset_status import ChangesetStatusModel
38 from kallithea.model.changeset_status import ChangesetStatusModel
39 from kallithea.model.pull_request import CreatePullRequestAction#, CreatePullRequestIterationAction, PullRequestModel
40 from kallithea.lib import helpers
41 from kallithea.lib.auth import AuthUser
35 from kallithea.lib.db_manage import DbManage
42 from kallithea.lib.db_manage import DbManage
36 from kallithea.lib.vcs.backends.base import EmptyChangeset
43 from kallithea.lib.vcs.backends.base import EmptyChangeset
37 from kallithea.tests.base import invalidate_all_caches, GIT_REPO, HG_REPO, TESTS_TMP_PATH, TEST_USER_ADMIN_LOGIN
44 from kallithea.tests.base import invalidate_all_caches, GIT_REPO, HG_REPO, \
45 TESTS_TMP_PATH, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
38
46
39
47
40 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
@@ -317,6 +325,21 b' class Fixture(object):'
317 Session().commit()
325 Session().commit()
318 return csm
326 return csm
319
327
328 def create_pullrequest(self, testcontroller, repo_name, pr_src_rev, pr_dst_rev, title='title'):
329 org_ref = 'branch:stable:%s' % pr_src_rev
330 other_ref = 'branch:default:%s' % pr_dst_rev
331 with test_context(testcontroller.app): # needed to be able to mock request user
332 org_repo = other_repo = Repository.get_by_repo_name(repo_name)
333 owner_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
334 reviewers = [User.get_by_username(TEST_USER_REGULAR_LOGIN)]
335 request.authuser = request.user = AuthUser(dbuser=owner_user)
336 # creating a PR sends a message with an absolute URL - without routing that requires mocking
337 with mock.patch.object(helpers, 'url', (lambda arg, qualified=False, **kwargs: ('https://localhost' if qualified else '') + '/fake/' + arg)):
338 cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, 'No description', owner_user, reviewers)
339 pull_request = cmd.execute()
340 Session().commit()
341 return pull_request.pull_request_id
342
320
343
321 #==============================================================================
344 #==============================================================================
322 # Global test environment setup
345 # Global test environment setup
General Comments 0
You need to be logged in to leave comments. Login now