diff --git a/pagure/api/project.py b/pagure/api/project.py index f51684c..1839828 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -66,6 +66,83 @@ def api_git_tags(repo, username=None, namespace=None): return jsonout +@API.route('//watchers') +@API.route('///watchers') +@API.route('/fork///watchers') +@API.route('/fork////watchers') +@api_method +def api_project_watchers(repo, username=None, namespace=None): + ''' + Project watchers + ---------------- + List the watchers on the project. + + :: + + GET /api/0//watchers + GET /api/0///watchers + + :: + + GET /api/0/fork///watchers + GET /api/0/fork////watchers + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "total_watchers": 1, + "watchers": { + "mprahl": [ + "issues", + "commits" + ] + } + } + ''' + repo = pagure.get_authorized_project( + SESSION, repo, user=username, namespace=namespace) + if repo is None: + raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) + + implicit_watch_users = {repo.user.username} + for access_type in repo.access_users.keys(): + implicit_watch_users = \ + implicit_watch_users | set( + [user.username for user in repo.access_users[access_type]]) + for access_type in repo.access_groups.keys(): + group_names = [group.group_name + for group in repo.access_groups[access_type]] + for group_name in group_names: + group = pagure.lib.search_groups(SESSION, group_name=group_name) + implicit_watch_users = \ + implicit_watch_users | set([ + user.username for user in group.users]) + + watching_users_to_watch_level = {} + for implicit_watch_user in implicit_watch_users: + user_watch_level = pagure.lib.get_watch_level_on_repo( + SESSION, implicit_watch_user, repo) + watching_users_to_watch_level[implicit_watch_user] = user_watch_level + + # Get the explicit watch statuses + for watcher in repo.watchers: + if watcher.watch_issues or watcher.watch_commits: + watching_users_to_watch_level[watcher.user.username] = \ + pagure.lib.get_watch_level_on_repo( + SESSION, watcher.user.username, repo) + else: + if watcher.user.username in watching_users_to_watch_level: + watching_users_to_watch_level.pop(watcher.user.username, None) + + return flask.jsonify({ + 'total_watchers': len(watching_users_to_watch_level), + 'watchers': watching_users_to_watch_level + }) + + @API.route('//git/branches') @API.route('///git/branches') @API.route('/fork///git/branches') diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 0890b19..bfcb038 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -3616,47 +3616,47 @@ def update_watch_status(session, project, user, watch): return 'You are no longer watching this project' -def get_watch_level_on_repo(session, user, reponame, repouser=None, +def get_watch_level_on_repo(session, user, repo, repouser=None, namespace=None): ''' Get a list representing the watch level of the user on the project. ''' # If a user wasn't passed in, we can't determine their watch level if user is None: return [] + elif isinstance(user, six.string_types): + user_obj = search_user(session, username=user) + else: + user_obj = search_user(session, username=user.username) # If we can't find the user in the database, we can't determine their watch # level - user_obj = search_user(session, username=user.username) if not user_obj: return [] + # If the user passed in a Project for the repo parameter, then we don't + # need to query for it + if isinstance(repo, model.Project): + project = repo + # If the user passed in a string, then assume it is a project name + elif isinstance(repo, six.string_types): + project = pagure.get_authorized_project( + session, repo, user=repouser, namespace=namespace) + else: + raise RuntimeError('The passed in repo is an invalid type of "{0}"' + .format(type(repo).__name__)) + + # If the project is not found, we can't determine the involvement of the + # user in the project + if not project: + return [] + query = session.query( model.Watcher ).filter( model.Watcher.user_id == user_obj.id ).filter( model.Watcher.project_id == model.Project.id - ).filter( - model.Project.name == reponame ) - if repouser is not None: - query = query.filter( - model.User.user == repouser - ).filter( - model.User.id == model.Project.user_id - ).filter( - model.Project.is_fork == True # noqa: E712 - ) - else: - query = query.filter( - model.Project.is_fork == False # noqa: E712 - ) - - if namespace is not None: - query = query.filter( - model.Project.namespace == namespace - ) - watcher = query.first() # If there is a watcher issue, that means the user explicitly set a watch # level on the project @@ -3672,27 +3672,20 @@ def get_watch_level_on_repo(session, user, reponame, repouser=None, # the user explicitly asked to not be notified return [] - project = pagure.get_authorized_project( - session, reponame, user=repouser, namespace=namespace) - - # If the project is not found, we can't determine the involvement of the - # user in the project - if not project: - return [] # If the user is the project owner, by default they will be watching # issues and PRs - if user.username == project.user.username: + if user_obj.username == project.user.username: return ['issues'] # If the user is a contributor, by default they will be watching issues # and PRs for contributor in project.users: - if user.username == contributor.username: + if user_obj.username == contributor.username: return ['issues'] # If the user is in a project group, by default they will be watching # issues and PRs for group in project.groups: for guser in group.users: - if user.username == guser.username: + if user_obj.username == guser.username: return ['issues'] # If no other condition is true, then they are not explicitly watching the # project or are not involved in the project to the point that comes with a diff --git a/tests/test_pagure_flask_api_project.py b/tests/test_pagure_flask_api_project.py index 46e5b40..baa179d 100644 --- a/tests/test_pagure_flask_api_project.py +++ b/tests/test_pagure_flask_api_project.py @@ -532,6 +532,194 @@ class PagureFlaskApiProjecttests(tests.Modeltests): } self.assertDictEqual(data, expected_data) + def test_api_project_watchers(self): + """ Test the api_project_watchers method of the flask api. """ + tests.create_projects(self.session) + # The user is not logged in and the owner is watching issues implicitly + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 1, + "watchers": { + "pingou": [ + "issues" + ] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + + user = pagure.SESSION.query(pagure.lib.model.User).filter_by( + user='pingou') + with tests.user_set(pagure.APP, user): + # Non-existing project + output = self.app.get('/api/0/random/watchers') + self.assertEqual(output.status_code, 404) + data = json.loads(output.data) + self.assertDictEqual( + data, + {'error_code': 'ENOPROJECT', 'error': 'Project not found'} + ) + + # The owner is watching issues implicitly + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 1, + "watchers": { + "pingou": [ + "issues" + ] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + + project = pagure.get_authorized_project(self.session, 'test') + + # The owner is watching issues and commits explicitly + pagure.lib.update_watch_status( + pagure.SESSION, project, 'pingou', '3') + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 1, + "watchers": { + "pingou": [ + "issues", + "commits" + ] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + + # The owner is watching issues explicitly + pagure.lib.update_watch_status( + pagure.SESSION, project, 'pingou', '1') + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 1, + "watchers": { + "pingou": [ + "issues" + ] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + + # The owner is watching commits explicitly + pagure.lib.update_watch_status( + pagure.SESSION, project, 'pingou', '2') + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 1, + "watchers": { + "pingou": [ + "commits" + ] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + + # The owner is watching commits explicitly and foo is watching + # issues implicitly + project_user = pagure.lib.model.ProjectUser( + project_id=project.id, + user_id=2, + access='commit', + ) + pagure.lib.update_watch_status( + pagure.SESSION, project, 'pingou', '2') + pagure.SESSION.add(project_user) + pagure.SESSION.commit() + + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 2, + "watchers": { + "foo": ["issues"], + "pingou": ["commits"] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + + # The owner and foo are watching issues implicitly + pagure.lib.update_watch_status( + pagure.SESSION, project, 'pingou', '-1') + + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 2, + "watchers": { + "foo": ["issues"], + "pingou": ["issues"] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + + # The owner and foo through group membership are watching issues + # implicitly + pagure.lib.update_watch_status( + pagure.SESSION, project, 'pingou', '-1') + project_membership = pagure.SESSION.query( + pagure.lib.model.ProjectUser).filter_by( + user_id=2, project_id=project.id).one() + pagure.SESSION.delete(project_membership) + pagure.SESSION.commit() + msg = pagure.lib.add_group( + self.session, + group_name='some_group', + display_name='Some Group', + description=None, + group_type='bar', + user='pingou', + is_admin=False, + blacklist=[], + ) + pagure.SESSION.commit() + group = pagure.SESSION.query(pagure.lib.model.PagureGroup)\ + .filter_by(group_name='some_group').one() + pagure.lib.add_user_to_group( + pagure.SESSION, 'foo', group, 'pingou', False) + project_group = pagure.lib.model.ProjectGroup( + project_id=project.id, + group_id=group.id, + access='commit', + ) + pagure.SESSION.add(project_group) + pagure.SESSION.commit() + + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 2, + "watchers": { + "foo": ["issues"], + "pingou": ["issues"] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + + # The owner is watching issues implicitly and foo will be watching + # commits explicitly but is in a group with commit access + pagure.lib.update_watch_status( + pagure.SESSION, project, 'pingou', '-1') + pagure.lib.update_watch_status( + pagure.SESSION, project, 'foo', '2') + + output = self.app.get('/api/0/test/watchers') + self.assertEqual(output.status_code, 200) + expected_data = { + "total_watchers": 2, + "watchers": { + "foo": ["commits"], + "pingou": ["issues"] + } + } + self.assertDictEqual(json.loads(output.data), expected_data) + @patch('pagure.lib.git.generate_gitolite_acls') def test_api_new_project(self, p_gga): """ Test the api_new_project method of the flask api. """ diff --git a/tests/test_pagure_lib.py b/tests/test_pagure_lib.py index 58e6363..a0d648c 100644 --- a/tests/test_pagure_lib.py +++ b/tests/test_pagure_lib.py @@ -3208,7 +3208,7 @@ class PagureLibtests(tests.Modeltests): watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=None, - reponame='test', + repo='test', ) self.assertEqual(watch_level, []) @@ -3218,7 +3218,7 @@ class PagureLibtests(tests.Modeltests): watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=user, - reponame='test', + repo='test', ) self.assertEqual(watch_level, []) @@ -3246,7 +3246,7 @@ class PagureLibtests(tests.Modeltests): msg = watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=user, - reponame='test', + repo='test', ) self.assertEqual(watch_level, ['issues']) @@ -3255,7 +3255,7 @@ class PagureLibtests(tests.Modeltests): watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=user, - reponame='test', + repo='test', ) self.assertEqual(watch_level, ['issues']) @@ -3275,7 +3275,7 @@ class PagureLibtests(tests.Modeltests): watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=user, - reponame='test', + repo='test', ) self.assertEqual(['issues', 'commits'], watch_level) @@ -3294,7 +3294,7 @@ class PagureLibtests(tests.Modeltests): watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=user, - reponame='test', + repo='test', ) self.assertEqual(['commits'], watch_level) @@ -3313,7 +3313,7 @@ class PagureLibtests(tests.Modeltests): watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=user, - reponame='test', + repo='test', ) self.assertEqual(['issues'], watch_level) @@ -3331,7 +3331,7 @@ class PagureLibtests(tests.Modeltests): watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=user, - reponame='test', + repo='test', ) self.assertEqual(watch_level, []) @@ -3361,7 +3361,7 @@ class PagureLibtests(tests.Modeltests): watch_level = pagure.lib.get_watch_level_on_repo( session=self.session, user=user, - reponame='test', + repo='test', ) self.assertEqual(watch_level, ['issues'])