From 9953756a6b7eb70f07c7b31ef042efcb49433de7 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 12:46:59 +0000 Subject: [PATCH 1/10] Expand the search_projects method to allow filtering projects by their tag --- diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 4926dc1..92200d2 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -1172,7 +1172,7 @@ def fork_project(session, user, repo, gitfolder, def search_projects( - session, username=None, fork=None, + session, username=None, fork=None, tags=None, start=None, limit=None, count=False): '''List existing projects ''' @@ -1236,6 +1236,16 @@ def search_projects( model.Project.parent_id == None ) + if tags is not None: + if not isinstance(tags, (list, tuple)): + tags = [tags] + + projects = projects.filter( + model.Project.id == model.TagProject.project_id + ).filter( + model.TagProject.tag.in_(tags) + ) + query = session.query( model.Project ).filter( From 47269dfd0c420a8c90463c5c23b73eccc6d9bebd Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 12:54:03 +0000 Subject: [PATCH 2/10] Add an API endpoint to search projects based on some given criteria This allows finding easily all the projects an user has commit to (restricted to their fork or not), or finding all the projects having a specific tag/tags. --- diff --git a/pagure/api/project.py b/pagure/api/project.py index 41aa382..1a58360 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -53,3 +53,82 @@ def api_git_tags(repo, username=None): jsonout = flask.jsonify({'tags': tags}) return jsonout + + +@API.route('/projects') +@api_method +def api_projects(): + """ + List projects + -------------- + Search projects given the specified criterias. + + :: + + GET /api/0/projects + + :: + + GET /api/0/projects?tags=fedora-infra + + Parameters + ^^^^^^^^^^ + + +---------------+----------+---------------+--------------------------+ + | Key | Type | Optionality | Description | + +===============+==========+===============+==========================+ + | ``tags`` | string | Optional | | Filters the projects | + | | | | returned by their tags | + +---------------+----------+---------------+--------------------------+ + | ``username`` | string | Optional | | Filters the projects | + | | | | returned by the users | + | | | | having commit rights | + | | | | to it | + +---------------+----------+---------------+--------------------------+ + | ``fork`` | boolean | Optional | | Filters the projects | + | | | | returned depending if | + | | | | they are forks or not | + +---------------+----------+---------------+--------------------------+ + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "projects": [ + "https://pagure.org/fedmsg", + "https://pagure.org/fork/pingou/fedmsg" + ] + } + + """ + tags = flask.request.values.getlist('tags') + username = flask.request.values.get('username', None) + fork = flask.request.values.get('fork', None) + + if str(fork).lower() in ['1', 'true']: + fork = True + elif str(fork).lower() in ['0', 'false']: + fork = False + + projects = pagure.lib.search_projects( + SESSION, username=username, fork=fork, tags=tags) + + if not projects: + raise pagure.exceptions.APIError( + 404, error_code=APIERROR.ENOPROJECTS) + + root = pagure.APP.config['APP_URL'] + if root.endswith('/'): + root = root[:-1] + + jsonout = flask.jsonify({ + 'projects': [ + root + flask.url_for( + 'view_repo', + repo=p.name, + username=p.user.username if p.is_fork else None) + for p in projects + ]}) + return jsonout From 16f973d3b061dcded3b7fca5dc782046c4a8483e Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 12:55:27 +0000 Subject: [PATCH 3/10] Document the new API endpoint listing projects in the main API documentation page --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index 257f9cd..8b5aeb9 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -410,6 +410,7 @@ def api_error_codes(): def api(): ''' Display the api information page. ''' api_git_tags_doc = load_doc(project.api_git_tags) + api_projects_doc = load_doc(project.api_projects) api_new_issue_doc = load_doc(issue.api_new_issue) api_view_issue_doc = load_doc(issue.api_view_issue) @@ -437,6 +438,7 @@ def api(): api_doc=APIDOC, projects=[ api_git_tags_doc, + api_projects_doc, ], issues=[ api_new_issue_doc, From bfd02d6bdc0186415f91c1cb9b712aa813688a31 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 13:07:16 +0000 Subject: [PATCH 4/10] Add API error code when no projects are found when searching --- diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py index 8b5aeb9..73dd47d 100644 --- a/pagure/api/__init__.py +++ b/pagure/api/__init__.py @@ -52,6 +52,7 @@ class APIERROR(enum.Enum): """ ENOCODE = 'Variable message describing the issue' ENOPROJECT = 'Project not found' + ENOPROJECTS = 'No projects found' ETRACKERDISABLED = 'Issue tracker disabled for this project' EDBERROR = 'An error occured at the database level and prevent the ' \ 'action from reaching completion' From ceb2625dd2244852adb96666e10bde994c04578f Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 13:07:44 +0000 Subject: [PATCH 5/10] Add unit-tests for the api_projects endpoint --- diff --git a/tests/test_progit_flask_api_project.py b/tests/test_progit_flask_api_project.py index 957f590..3e56472 100644 --- a/tests/test_progit_flask_api_project.py +++ b/tests/test_progit_flask_api_project.py @@ -102,6 +102,41 @@ class PagureFlaskApiProjecttests(tests.Modeltests): {'tags': ['0.0.1']} ) + def test_api_projects(self): + """ Test the api_projects method of the flask api. """ + tests.create_projects(self.session) + + # Check before adding + repo = pagure.lib.get_project(self.session, 'test') + self.assertEqual(repo.tags, []) + + # Adding a tag + output = pagure.lib.update_tags( + self.session, repo, 'infra', 'pingou', + ticketfolder=None, redis=None) + self.assertEqual(output, ['Tag added: infra']) + + # Check after adding + repo = pagure.lib.get_project(self.session, 'test') + self.assertEqual(len(repo.tags), 1) + self.assertEqual(repo.tags_text, ['infra']) + + # Check the API + output = self.app.get('/api/0/projects?tags=inf') + self.assertEqual(output.status_code, 404) + data = json.loads(output.data) + self.assertDictEqual( + data, + {'error_code': 'ENOPROJECTS', 'error': 'No projects found'} + ) + output = self.app.get('/api/0/projects?tags=infra') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + self.assertDictEqual( + data, + {'projects': ['https://pagure.org/test']} + ) + if __name__ == '__main__': SUITE = unittest.TestLoader().loadTestsFromTestCase( From 1942c15080d8a5cd1c14245879d513eb2d3fa84e Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 13:16:42 +0000 Subject: [PATCH 6/10] Tags should not be None, but not an empty list either --- diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 92200d2..139a5c7 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -1236,7 +1236,7 @@ def search_projects( model.Project.parent_id == None ) - if tags is not None: + if tags: if not isinstance(tags, (list, tuple)): tags = [tags] From 7485e274f9f6c4e2fe66b7627089b4f13ca8709a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 13:18:19 +0000 Subject: [PATCH 7/10] More unit-tests for api_projects --- diff --git a/tests/test_progit_flask_api_project.py b/tests/test_progit_flask_api_project.py index 3e56472..daf571c 100644 --- a/tests/test_progit_flask_api_project.py +++ b/tests/test_progit_flask_api_project.py @@ -136,6 +136,21 @@ class PagureFlaskApiProjecttests(tests.Modeltests): data, {'projects': ['https://pagure.org/test']} ) + output = self.app.get('/api/0/projects?username=pingou') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + self.assertDictEqual( + data, + {'projects': + ['https://pagure.org/test', 'https://pagure.org/test2']} + ) + output = self.app.get('/api/0/projects?username=pingou&tags=infra') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + self.assertDictEqual( + data, + {'projects': ['https://pagure.org/test']} + ) if __name__ == '__main__': From 05ddaf36b0ac9a67cd35b50b2155908bfe3b5cab Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 13:29:22 +0000 Subject: [PATCH 8/10] Update api_projects to return the JSON representation of the projects This should make it easier to consume this URL endpoint than just returning the URLs --- diff --git a/pagure/api/project.py b/pagure/api/project.py index 1a58360..970f849 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -97,8 +97,38 @@ def api_projects(): { "projects": [ - "https://pagure.org/fedmsg", - "https://pagure.org/fork/pingou/fedmsg" + { + "date_created": "1427441537", + "description": "A web-based calendar for Fedora", + "id": 7, + "name": "fedocal", + "parent": null, + "user": { + "fullname": "Pierre-Yves C", + "name": "pingou" + } + }, + { + "date_created": "1431666007", + "description": "An awesome messaging servicefor everyone", + "id": 12, + "name": "fedmsg", + "parent": { + "date_created": "1433423298", + "description": "An awesome messaging servicefor everyone", + "id": 11, + "name": "fedmsg", + "parent": null, + "user": { + "fullname": "Ralph B", + "name": "ralph" + } + }, + "user": { + "fullname": "Pierre-Yves C", + "name": "pingou" + } + } ] } @@ -124,11 +154,6 @@ def api_projects(): root = root[:-1] jsonout = flask.jsonify({ - 'projects': [ - root + flask.url_for( - 'view_repo', - repo=p.name, - username=p.user.username if p.is_fork else None) - for p in projects - ]}) + 'projects': [ p.to_json(api=True, public=True) for p in projects] + }) return jsonout From 07a462b2cdaf27b0d40b5d216a9f7403327ae60b Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 13:30:15 +0000 Subject: [PATCH 9/10] Adjust the unit-tests for the change in the output returned by api_projects --- diff --git a/tests/test_progit_flask_api_project.py b/tests/test_progit_flask_api_project.py index daf571c..555f5bd 100644 --- a/tests/test_progit_flask_api_project.py +++ b/tests/test_progit_flask_api_project.py @@ -132,24 +132,80 @@ class PagureFlaskApiProjecttests(tests.Modeltests): output = self.app.get('/api/0/projects?tags=infra') self.assertEqual(output.status_code, 200) data = json.loads(output.data) + data['projects'][0]['date_created'] = "1436527638" self.assertDictEqual( data, - {'projects': ['https://pagure.org/test']} + { + "projects": [ + { + "date_created": "1436527638", + "description": "test project #1", + "id": 1, + "name": "test", + "parent": None, + "user": { + "fullname": "PY C", + "name": "pingou" + } + } + ] + } ) output = self.app.get('/api/0/projects?username=pingou') self.assertEqual(output.status_code, 200) data = json.loads(output.data) + data['projects'][0]['date_created'] = "1436527638" + data['projects'][1]['date_created'] = "1436527638" self.assertDictEqual( data, - {'projects': - ['https://pagure.org/test', 'https://pagure.org/test2']} + { + "projects": [ + { + "date_created": "1436527638", + "description": "test project #1", + "id": 1, + "name": "test", + "parent": None, + "user": { + "fullname": "PY C", + "name": "pingou" + } + }, + { + "date_created": "1436527638", + "description": "test project #2", + "id": 2, + "name": "test2", + "parent": None, + "user": { + "fullname": "PY C", + "name": "pingou" + } + } + ] + } ) output = self.app.get('/api/0/projects?username=pingou&tags=infra') self.assertEqual(output.status_code, 200) data = json.loads(output.data) + data['projects'][0]['date_created'] = "1436527638" self.assertDictEqual( data, - {'projects': ['https://pagure.org/test']} + { + "projects": [ + { + "date_created": "1436527638", + "description": "test project #1", + "id": 1, + "name": "test", + "parent": None, + "user": { + "fullname": "PY C", + "name": "pingou" + } + } + ] + } ) From 64002f751b767a8bfdb9ae6306631a387492aadf Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 10 2015 13:37:03 +0000 Subject: [PATCH 10/10] Drop the root variable, we no longer use it --- diff --git a/pagure/api/project.py b/pagure/api/project.py index 970f849..a568557 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -149,10 +149,6 @@ def api_projects(): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ENOPROJECTS) - root = pagure.APP.config['APP_URL'] - if root.endswith('/'): - root = root[:-1] - jsonout = flask.jsonify({ 'projects': [ p.to_json(api=True, public=True) for p in projects] })