diff --git a/rhodecode/api/tests/test_comment_commit.py b/rhodecode/api/tests/test_comment_commit.py --- a/rhodecode/api/tests/test_comment_commit.py +++ b/rhodecode/api/tests/test_comment_commit.py @@ -20,7 +20,7 @@ import pytest -from rhodecode.model.db import ChangesetStatus +from rhodecode.model.db import ChangesetStatus, User from rhodecode.api.tests.utils import ( build_data, api_call, assert_error, assert_ok) @@ -79,3 +79,38 @@ class TestCommentCommit(object): 'success': True } assert_ok(id_, expected, given=response.body) + + def test_api_comment_commit_with_extra_recipients(self, backend, user_util): + + commit_id = backend.repo.scm_instance().get_commit('tip').raw_id + + user1 = user_util.create_user() + user1_id = user1.user_id + user2 = user_util.create_user() + user2_id = user2.user_id + + id_, params = build_data( + self.apikey, 'comment_commit', repoid=backend.repo_name, + commit_id=commit_id, + message='abracadabra', + extra_recipients=[user1.user_id, user2.username]) + + response = api_call(self.app, params) + repo = backend.repo.scm_instance() + + expected = { + 'msg': 'Commented on commit `%s` for repository `%s`' % ( + repo.get_commit().raw_id, backend.repo_name), + 'status_change': None, + 'success': True + } + + assert_ok(id_, expected, given=response.body) + # check user1/user2 inbox for notification + user1 = User.get(user1_id) + assert 1 == len(user1.notifications) + assert 'abracadabra' in user1.notifications[0].notification.body + + user2 = User.get(user2_id) + assert 1 == len(user2.notifications) + assert 'abracadabra' in user2.notifications[0].notification.body diff --git a/rhodecode/api/tests/test_comment_pull_request.py b/rhodecode/api/tests/test_comment_pull_request.py --- a/rhodecode/api/tests/test_comment_pull_request.py +++ b/rhodecode/api/tests/test_comment_pull_request.py @@ -21,7 +21,7 @@ import pytest from rhodecode.model.comment import CommentsModel -from rhodecode.model.db import UserLog +from rhodecode.model.db import UserLog, User from rhodecode.model.pull_request import PullRequestModel from rhodecode.tests import TEST_USER_ADMIN_LOGIN from rhodecode.api.tests.utils import ( @@ -70,6 +70,43 @@ class TestCommentPullRequest(object): assert journal[-1].action == 'repo.pull_request.comment.create' @pytest.mark.backends("git", "hg") + def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util): + pull_request = pr_util.create_pull_request() + + user1 = user_util.create_user() + user1_id = user1.user_id + user2 = user_util.create_user() + user2_id = user2.user_id + + id_, params = build_data( + self.apikey, 'comment_pull_request', + repoid=pull_request.target_repo.repo_name, + pullrequestid=pull_request.pull_request_id, + message='test message', + extra_recipients=[user1.user_id, user2.username] + ) + response = api_call(self.app, params) + pull_request = PullRequestModel().get(pull_request.pull_request_id) + + comments = CommentsModel().get_comments( + pull_request.target_repo.repo_id, pull_request=pull_request) + + expected = { + 'pull_request_id': pull_request.pull_request_id, + 'comment_id': comments[-1].comment_id, + 'status': {'given': None, 'was_changed': None} + } + assert_ok(id_, expected, response.body) + # check user1/user2 inbox for notification + user1 = User.get(user1_id) + assert 1 == len(user1.notifications) + assert 'test message' in user1.notifications[0].notification.body + + user2 = User.get(user2_id) + assert 1 == len(user2.notifications) + assert 'test message' in user2.notifications[0].notification.body + + @pytest.mark.backends("git", "hg") def test_api_comment_pull_request_change_status( self, pr_util, no_notifications): pull_request = pr_util.create_pull_request() diff --git a/rhodecode/api/tests/test_get_method.py b/rhodecode/api/tests/test_get_method.py --- a/rhodecode/api/tests/test_get_method.py +++ b/rhodecode/api/tests/test_get_method.py @@ -50,6 +50,7 @@ class TestGetMethod(object): {'apiuser': '', 'comment_type': "", 'commit_id': '', + 'extra_recipients': '', 'message': '', 'repoid': '', 'request': '', diff --git a/rhodecode/api/views/pull_request_api.py b/rhodecode/api/views/pull_request_api.py --- a/rhodecode/api/views/pull_request_api.py +++ b/rhodecode/api/views/pull_request_api.py @@ -451,7 +451,7 @@ def comment_pull_request( request, apiuser, pullrequestid, repoid=Optional(None), message=Optional(None), commit_id=Optional(None), status=Optional(None), comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE), - resolves_comment_id=Optional(None), + resolves_comment_id=Optional(None), extra_recipients=Optional([]), userid=Optional(OAttr('apiuser'))): """ Comment on the pull request specified with the `pullrequestid`, @@ -476,6 +476,11 @@ def comment_pull_request( :type status: str :param comment_type: Comment type, one of: 'note', 'todo' :type comment_type: Optional(str), default: 'note' + :param resolves_comment_id: id of comment which this one will resolve + :type resolves_comment_id: Optional(int) + :param extra_recipients: list of user ids or usernames to add + notifications for this comment. Acts like a CC for notification + :type extra_recipients: Optional(list) :param userid: Comment on the pull request as this user :type userid: Optional(str or int) @@ -521,6 +526,7 @@ def comment_pull_request( commit_id = Optional.extract(commit_id) comment_type = Optional.extract(comment_type) resolves_comment_id = Optional.extract(resolves_comment_id) + extra_recipients = Optional.extract(extra_recipients) if not message and not status: raise JSONRPCError( @@ -580,7 +586,8 @@ def comment_pull_request( renderer=renderer, comment_type=comment_type, resolves_comment_id=resolves_comment_id, - auth_user=auth_user + auth_user=auth_user, + extra_recipients=extra_recipients ) if allowed_to_change_status and status: diff --git a/rhodecode/api/views/repo_api.py b/rhodecode/api/views/repo_api.py --- a/rhodecode/api/views/repo_api.py +++ b/rhodecode/api/views/repo_api.py @@ -1550,7 +1550,7 @@ def lock(request, apiuser, repoid, locke def comment_commit( request, apiuser, repoid, commit_id, message, status=Optional(None), comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE), - resolves_comment_id=Optional(None), + resolves_comment_id=Optional(None), extra_recipients=Optional([]), userid=Optional(OAttr('apiuser'))): """ Set a commit comment, and optionally change the status of the commit. @@ -1568,6 +1568,11 @@ def comment_commit( :type status: str :param comment_type: Comment type, one of: 'note', 'todo' :type comment_type: Optional(str), default: 'note' + :param resolves_comment_id: id of comment which this one will resolve + :type resolves_comment_id: Optional(int) + :param extra_recipients: list of user ids or usernames to add + notifications for this comment. Acts like a CC for notification + :type extra_recipients: Optional(list) :param userid: Set the user name of the comment creator. :type userid: Optional(str or int) @@ -1604,6 +1609,7 @@ def comment_commit( status = Optional.extract(status) comment_type = Optional.extract(comment_type) resolves_comment_id = Optional.extract(resolves_comment_id) + extra_recipients = Optional.extract(extra_recipients) allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES] if status and status not in allowed_statuses: @@ -1632,7 +1638,8 @@ def comment_commit( renderer=renderer, comment_type=comment_type, resolves_comment_id=resolves_comment_id, - auth_user=apiuser + auth_user=apiuser, + extra_recipients=extra_recipients ) if status: # also do a status change diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -232,7 +232,7 @@ class CommentsModel(BaseModel): f_path=None, line_no=None, status_change=None, status_change_type=None, comment_type=None, resolves_comment_id=None, closing_pr=False, send_email=True, - renderer=None, auth_user=None): + renderer=None, auth_user=None, extra_recipients=None): """ Creates new comment for commit or pull request. IF status_change is not none this comment is associated with a @@ -247,10 +247,13 @@ class CommentsModel(BaseModel): :param line_no: :param status_change: Label for status change :param comment_type: Type of comment + :param resolves_comment_id: id of comment which this one will resolve :param status_change_type: type of status change :param closing_pr: :param send_email: :param renderer: pick renderer for this comment + :param auth_user: current authenticated user calling this method + :param extra_recipients: list of extra users to be added to recipients """ if not text: @@ -406,6 +409,9 @@ class CommentsModel(BaseModel): 'pr_comment_url': pr_comment_url, 'pr_closing': closing_pr, }) + + recipients += [self._get_user(u) for u in (extra_recipients or [])] + if send_email: # pre-generate the subject for notification itself (subject, diff --git a/rhodecode/model/notification.py b/rhodecode/model/notification.py --- a/rhodecode/model/notification.py +++ b/rhodecode/model/notification.py @@ -111,6 +111,7 @@ class NotificationModel(BaseModel): # add mentioned users into recipients final_recipients = set(recipients_objs).union(mention_recipients) + notification = Notification.create( created_by=created_by_obj, subject=notification_subject, body=notification_body, recipients=final_recipients,