diff --git a/rhodecode/api/tests/test_api.py b/rhodecode/api/tests/test_api.py --- a/rhodecode/api/tests/test_api.py +++ b/rhodecode/api/tests/test_api.py @@ -86,7 +86,9 @@ class TestApi(object): def test_api_non_existing_method_have_similar(self, request): id_, params = build_data(self.apikey, 'comment', args='xx') response = api_call(self.app, params) - expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, get_pull_request_comments, comment_commit' + expected = 'No such method: comment. ' \ + 'Similar methods: changeset_comment, comment_pull_request, ' \ + 'get_pull_request_comments, comment_commit, get_repo_comments' assert_error(id_, expected, given=response.body) def test_api_disabled_user(self, 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 @@ -38,7 +38,7 @@ class TestGetMethod(object): response = api_call(self.app, params) expected = ['changeset_comment', 'comment_pull_request', - 'get_pull_request_comments', 'comment_commit'] + 'get_pull_request_comments', 'comment_commit', 'get_repo_comments'] assert_ok(id_, expected, given=response.body) def test_get_methods_on_single_match(self): diff --git a/rhodecode/api/tests/test_get_pull_request_comments.py b/rhodecode/api/tests/test_get_pull_request_comments.py --- a/rhodecode/api/tests/test_get_pull_request_comments.py +++ b/rhodecode/api/tests/test_get_pull_request_comments.py @@ -59,6 +59,7 @@ class TestGetPullRequestComments(object) 'status_lbl': 'Under Review'}, 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*', 'comment_type': 'note', + 'comment_resolved_by': None, 'pull_request_version': None} ] assert_ok(id_, expected, response.body) diff --git a/rhodecode/api/tests/test_get_repo_comments.py b/rhodecode/api/tests/test_get_repo_comments.py new file mode 100644 --- /dev/null +++ b/rhodecode/api/tests/test_get_repo_comments.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2019 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + + +import pytest + +from rhodecode.model.db import User, ChangesetComment +from rhodecode.model.meta import Session +from rhodecode.model.comment import CommentsModel +from rhodecode.api.tests.utils import ( + build_data, api_call, assert_error, assert_call_ok) + + +@pytest.fixture() +def make_repo_comments_factory(request): + + def maker(repo): + user = User.get_first_super_admin() + commit = repo.scm_instance()[0] + + commit_id = commit.raw_id + file_0 = commit.affected_files[0] + comments = [] + + # general + CommentsModel().create( + text='General Comment', repo=repo, user=user, commit_id=commit_id, + comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False) + + # inline + CommentsModel().create( + text='Inline Comment', repo=repo, user=user, commit_id=commit_id, + f_path=file_0, line_no='n1', + comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False) + + # todo + CommentsModel().create( + text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id, + f_path=file_0, line_no='n1', + comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False) + + @request.addfinalizer + def cleanup(): + for comment in comments: + Session().delete(comment) + return maker + + +@pytest.mark.usefixtures("testuser_api", "app") +class TestGetRepo(object): + + @pytest.mark.parametrize('filters, expected_count', [ + ({}, 3), + ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2), + ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1), + ({'commit_id': 'FILLED DYNAMIC'}, 3), + ]) + def test_api_get_repo_comments(self, backend, user_util, + make_repo_comments_factory, filters, expected_count): + commits = [{'message': 'A'}, {'message': 'B'}] + repo = backend.create_repo(commits=commits) + make_repo_comments_factory(repo) + + api_call_params = {'repoid': repo.repo_name,} + api_call_params.update(filters) + + if 'commit_id' in api_call_params: + commit = repo.scm_instance()[0] + commit_id = commit.raw_id + api_call_params['commit_id'] = commit_id + + id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params) + response = api_call(self.app, params) + result = assert_call_ok(id_, given=response.body) + + assert len(result) == expected_count + + def test_api_get_repo_comments_wrong_comment_typ(self, backend_hg): + + repo = backend_hg.create_repo() + make_repo_comments_factory(repo) + + api_call_params = {'repoid': repo.repo_name,} + api_call_params.update({'comment_type': 'bogus'}) + + expected = 'comment_type must be one of `{}` got {}'.format( + ChangesetComment.COMMENT_TYPES, 'bogus') + id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params) + response = api_call(self.app, params) + assert_error(id_, expected, given=response.body) diff --git a/rhodecode/api/tests/utils.py b/rhodecode/api/tests/utils.py --- a/rhodecode/api/tests/utils.py +++ b/rhodecode/api/tests/utils.py @@ -28,6 +28,19 @@ from rhodecode.lib.ext_json import json API_URL = '/_admin/api' +def assert_call_ok(id_, given): + expected = jsonify({ + 'id': id_, + 'error': None, + 'result': None + }) + given = json.loads(given) + + assert expected['id'] == given['id'] + assert expected['error'] == given['error'] + return given['result'] + + def assert_ok(id_, expected, given): expected = jsonify({ 'id': id_, @@ -55,8 +68,6 @@ def jsonify(obj): def build_data(apikey, method, **kw): """ Builds API data with given random ID - - :param random_id: """ random_id = random.randrange(1, 9999) return random_id, json.dumps({ 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 @@ -1506,6 +1506,73 @@ def comment_commit( @jsonrpc_method() +def get_repo_comments(request, apiuser, repoid, + commit_id=Optional(None), comment_type=Optional(None), + userid=Optional(None)): + """ + Get all comments for a repository + + :param apiuser: This is filled automatically from the |authtoken|. + :type apiuser: AuthUser + :param repoid: Set the repository name or repository ID. + :type repoid: str or int + :param commit_id: Optionally filter the comments by the commit_id + :type commit_id: Optional(str), default: None + :param comment_type: Optionally filter the comments by the comment_type + one of: 'note', 'todo' + :type comment_type: Optional(str), default: None + :param userid: Optionally filter the comments by the author of comment + :type userid: Optional(str or int), Default: None + + Example error output: + + .. code-block:: bash + + { + "id" : , + "result" : [ + { + "comment_author": , + "comment_created_on": "2017-02-01T14:38:16.309", + "comment_f_path": "file.txt", + "comment_id": 282, + "comment_lineno": "n1", + "comment_resolved_by": null, + "comment_status": [], + "comment_text": "This file needs a header", + "comment_type": "todo" + } + ], + "error" : null + } + + """ + repo = get_repo_or_error(repoid) + if not has_superadmin_permission(apiuser): + _perms = ('repository.read', 'repository.write', 'repository.admin') + validate_repo_permissions(apiuser, repoid, repo, _perms) + + commit_id = Optional.extract(commit_id) + + userid = Optional.extract(userid) + if userid: + user = get_user_or_error(userid) + else: + user = None + + comment_type = Optional.extract(comment_type) + if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES: + raise JSONRPCError( + 'comment_type must be one of `{}` got {}'.format( + ChangesetComment.COMMENT_TYPES, comment_type) + ) + + comments = CommentsModel().get_repository_comments( + repo=repo, comment_type=comment_type, user=user, commit_id=commit_id) + return comments + + +@jsonrpc_method() def grant_user_permission(request, apiuser, repoid, userid, perm): """ Grant permissions for the specified user on the given repository, diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -125,6 +125,24 @@ class CommentsModel(BaseModel): return comment_versions + def get_repository_comments(self, repo, comment_type=None, user=None, commit_id=None): + qry = Session().query(ChangesetComment) \ + .filter(ChangesetComment.repo == repo) + + if comment_type and comment_type in ChangesetComment.COMMENT_TYPES: + qry = qry.filter(ChangesetComment.comment_type == comment_type) + + if user: + user = self._get_user(user) + if user: + qry = qry.filter(ChangesetComment.user_id == user.user_id) + + if commit_id: + qry = qry.filter(ChangesetComment.revision == commit_id) + + qry = qry.order_by(ChangesetComment.created_on) + return qry.all() + def get_repository_unresolved_todos(self, repo): todos = Session().query(ChangesetComment) \ .filter(ChangesetComment.repo == repo) \ diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -3497,7 +3497,8 @@ class ChangesetComment(Base, BaseModel): 'comment_f_path': comment.f_path, 'comment_lineno': comment.line_no, 'comment_author': comment.author, - 'comment_created_on': comment.created_on + 'comment_created_on': comment.created_on, + 'comment_resolved_by': self.resolved } return data