From 406464451ff8a5ad04aa439a4706966e5f8bcdca Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 16 2017 11:14:26 +0000 Subject: [PATCH 1/7] Allow filtering tickets by milestones in the API --- diff --git a/pagure/api/issue.py b/pagure/api/issue.py index 3df2471..9bff2da 100644 --- a/pagure/api/issue.py +++ b/pagure/api/issue.py @@ -226,6 +226,9 @@ def api_view_issues(repo, username=None, namespace=None): | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ + | ``milestones``| list of | Optional | | Filter the issues | + | | strings | | by milestone | + +---------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ @@ -282,6 +285,7 @@ def api_view_issues(repo, username=None, namespace=None): tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) + milestone = flask.request.args.getlist('milestones', None) since = flask.request.args.get('since', None) # Hide private tickets @@ -303,6 +307,7 @@ def api_view_issues(repo, username=None, namespace=None): 'assignee': assignee, 'author': author, 'private': private, + 'milestones': milestone, } if status is not None: @@ -342,6 +347,7 @@ def api_view_issues(repo, username=None, namespace=None): 'assignee': assignee, 'author': author, 'since': since + 'milestones': milestone, } }) return jsonout diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py index db066a8..861840d 100644 --- a/tests/test_pagure_flask_api_issue.py +++ b/tests/test_pagure_flask_api_issue.py @@ -781,6 +781,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": None, "tags": [] @@ -818,6 +819,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": None, "tags": [] @@ -857,6 +859,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": None, "tags": [] @@ -882,6 +885,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": None, "tags": [] @@ -921,6 +925,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": None, "tags": [] @@ -945,6 +950,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": None, "tags": [] @@ -964,6 +970,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": "Closed", "tags": [] @@ -983,6 +990,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": "Invalid", "tags": [] @@ -1005,6 +1013,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": "All", "tags": [] @@ -1140,6 +1149,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": None, "status": None, "tags": [] @@ -1165,6 +1175,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": start, "status": None, "tags": [] @@ -1187,6 +1198,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": middle, "status": None, "tags": [] @@ -1209,6 +1221,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": final, "status": None, "tags": [] @@ -1234,6 +1247,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "args": { "assignee": None, "author": None, + 'milestones': [], "since": final, "status": None, "tags": [] From bceef096176a13121bec47403e1284fa716955c2 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 16 2017 11:14:26 +0000 Subject: [PATCH 2/7] Add unit-tests checking the API behavior when filtering for a milestone --- diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py index 861840d..58b43ec 100644 --- a/tests/test_pagure_flask_api_issue.py +++ b/tests/test_pagure_flask_api_issue.py @@ -11,6 +11,7 @@ __requires__ = ['SQLAlchemy >= 0.8'] import pkg_resources +import copy import datetime import unittest import shutil @@ -238,6 +239,50 @@ FULL_ISSUE_LIST = [ ] +LCL_ISSUES = [ + { + 'assignee': None, + 'blocks': [], + 'close_status': None, + 'closed_at': None, + 'comments': [], + 'content': 'Description', + 'custom_fields': [], + 'date_created': '1431414800', + 'depends': [], + 'id': 2, + 'last_updated': '1431414800', + 'milestone': None, + 'priority': None, + 'private': False, + 'status': 'Open', + 'tags': [], + 'title': 'Issue #2', + 'user': {'fullname': 'PY C', 'name': 'pingou'} + }, + { + 'assignee': None, + 'blocks': [], + 'close_status': None, + 'closed_at': None, + 'comments': [], + 'content': 'Description', + 'custom_fields': [], + 'date_created': '1431414800', + 'depends': [], + 'id': 1, + 'last_updated': '1431414800', + 'milestone': None, + 'priority': None, + 'private': False, + 'status': 'Open', + 'tags': [], + 'title': 'Issue #1', + 'user': {'fullname': 'PY C', 'name': 'pingou'} + } +] + + class PagureFlaskApiIssuetests(tests.Modeltests): """ Tests for the flask API of pagure for issue """ def setUp(self): @@ -1023,10 +1068,96 @@ class PagureFlaskApiIssuetests(tests.Modeltests): } ) + def test_api_view_issues_milestone(self): + """ Test the api_view_issues method of the flask api when filtering + for a milestone. + """ + tests.create_projects(self.session) + tests.create_projects_git( + os.path.join(self.path, 'tickets'), bare=True) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + repo = pagure.lib.get_project(self.session, 'test') + + # Create 2 tickets but only 1 has a milestone + start = datetime.datetime.utcnow().strftime('%s') + issue = pagure.lib.model.Issue( + id=pagure.lib.get_next_id(self.session, repo.id), + project_id=repo.id, + title='Issue #1', + content='Description', + user_id=1, # pingou + uid='issue#1', + private=False, + ) + self.session.add(issue) + self.session.commit() + + issue = pagure.lib.model.Issue( + id=pagure.lib.get_next_id(self.session, repo.id), + project_id=repo.id, + title='Issue #2', + content='Description', + user_id=1, # pingou + uid='issue#2', + private=False, + milestone='v1.0', + ) + self.session.add(issue) + self.session.commit() + + # List all opened issues + output = self.app.get('/api/0/test/issues') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + for idx in range(len(data['issues'])): + data['issues'][idx]['date_created'] = '1431414800' + data['issues'][idx]['last_updated'] = '1431414800' + lcl_issues = copy.deepcopy(LCL_ISSUES) + lcl_issues[0]['milestone'] = 'v1.0' + self.assertDictEqual( + data, + { + "args": { + "assignee": None, + "author": None, + 'milestones': [], + "since": None, + "status": None, + "tags": [], + }, + "issues": lcl_issues, + "total_issues": 2 + } + ) + + # List all issues of the milestone v1.0 + output = self.app.get('/api/0/test/issues?milestones=v1.0') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + for idx in range(len(data['issues'])): + data['issues'][idx]['date_created'] = '1431414800' + data['issues'][idx]['last_updated'] = '1431414800' + self.assertDictEqual( + data, + { + "args": { + "assignee": None, + "author": None, + 'milestones': ['v1.0'], + "since": None, + "status": None, + "tags": [], + }, + "issues": [lcl_issues[0]], + "total_issues": 1 + } + ) + def test_api_view_issues_since(self): """ Test the api_view_issues method of the flask api for since option """ - tests.create_projects(self.session) tests.create_projects_git( os.path.join(self.path, 'tickets'), bare=True) @@ -1093,49 +1224,6 @@ class PagureFlaskApiIssuetests(tests.Modeltests): } ) - LCL_ISSUES = [ - { - 'assignee': None, - 'blocks': [], - 'close_status': None, - 'closed_at': None, - 'comments': [], - 'content': 'Description', - 'custom_fields': [], - 'date_created': '1431414800', - 'depends': [], - 'id': 2, - 'last_updated': '1431414800', - 'milestone': None, - 'priority': None, - 'private': False, - 'status': 'Open', - 'tags': [], - 'title': 'Issue #2', - 'user': {'fullname': 'PY C', 'name': 'pingou'} - }, - { - 'assignee': None, - 'blocks': [], - 'close_status': None, - 'closed_at': None, - 'comments': [], - 'content': 'Description', - 'custom_fields': [], - 'date_created': '1431414800', - 'depends': [], - 'id': 1, - 'last_updated': '1431414800', - 'milestone': None, - 'priority': None, - 'private': False, - 'status': 'Open', - 'tags': [], - 'title': 'Issue #1', - 'user': {'fullname': 'PY C', 'name': 'pingou'} - } - ] - # List all opened issues output = self.app.get('/api/0/test/issues') self.assertEqual(output.status_code, 200) From e34a6ffc480d30d7c6f79361d3936d12ff4d3117 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 16 2017 11:14:26 +0000 Subject: [PATCH 3/7] Expand the API to allow filtering issues by priority --- diff --git a/pagure/api/issue.py b/pagure/api/issue.py index 9bff2da..08906ea 100644 --- a/pagure/api/issue.py +++ b/pagure/api/issue.py @@ -229,6 +229,9 @@ def api_view_issues(repo, username=None, namespace=None): | ``milestones``| list of | Optional | | Filter the issues | | | strings | | by milestone | +---------------+---------+--------------+---------------------------+ + | ``priority`` | string | Optional | | Filter the issues | + | | | | by priority | + +---------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ @@ -286,6 +289,7 @@ def api_view_issues(repo, username=None, namespace=None): assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) milestone = flask.request.args.getlist('milestones', None) + priority = flask.request.args.get('priority', None) since = flask.request.args.get('since', None) # Hide private tickets @@ -308,6 +312,7 @@ def api_view_issues(repo, username=None, namespace=None): 'author': author, 'private': private, 'milestones': milestone, + 'priority': priority, } if status is not None: @@ -348,6 +353,7 @@ def api_view_issues(repo, username=None, namespace=None): 'author': author, 'since': since 'milestones': milestone, + 'priority': priority, } }) return jsonout diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py index 58b43ec..184e327 100644 --- a/tests/test_pagure_flask_api_issue.py +++ b/tests/test_pagure_flask_api_issue.py @@ -827,6 +827,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": None, "tags": [] @@ -865,6 +866,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": None, "tags": [] @@ -905,6 +907,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": None, "tags": [] @@ -931,6 +934,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": None, "tags": [] @@ -971,6 +975,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": None, "tags": [] @@ -996,6 +1001,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": None, "tags": [] @@ -1016,6 +1022,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": "Closed", "tags": [] @@ -1036,6 +1043,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": "Invalid", "tags": [] @@ -1059,6 +1067,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": "All", "tags": [] @@ -1123,6 +1132,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": None, "tags": [], @@ -1146,6 +1156,96 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': ['v1.0'], + 'priority': None, + "since": None, + "status": None, + "tags": [], + }, + "issues": [lcl_issues[0]], + "total_issues": 1 + } + ) + + def test_api_view_issues_priority(self): + """ Test the api_view_issues method of the flask api when filtering + for a priority. + """ + tests.create_projects(self.session) + tests.create_projects_git( + os.path.join(self.path, 'tickets'), bare=True) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + repo = pagure.lib.get_project(self.session, 'test') + + # Create 2 tickets but only 1 has a priority + start = datetime.datetime.utcnow().strftime('%s') + issue = pagure.lib.model.Issue( + id=pagure.lib.get_next_id(self.session, repo.id), + project_id=repo.id, + title='Issue #1', + content='Description', + user_id=1, # pingou + uid='issue#1', + private=False, + ) + self.session.add(issue) + self.session.commit() + + issue = pagure.lib.model.Issue( + id=pagure.lib.get_next_id(self.session, repo.id), + project_id=repo.id, + title='Issue #2', + content='Description', + user_id=1, # pingou + uid='issue#2', + private=False, + priority='high', + ) + self.session.add(issue) + self.session.commit() + + # List all opened issues + output = self.app.get('/api/0/test/issues') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + for idx in range(len(data['issues'])): + data['issues'][idx]['date_created'] = '1431414800' + data['issues'][idx]['last_updated'] = '1431414800' + lcl_issues = copy.deepcopy(LCL_ISSUES) + lcl_issues[0]['priority'] = 'high' + self.assertDictEqual( + data, + { + "args": { + "assignee": None, + "author": None, + 'milestones': [], + 'priority': None, + "since": None, + "status": None, + "tags": [], + }, + "issues": lcl_issues, + "total_issues": 2 + } + ) + + # List all issues of the milestone v1.0 + output = self.app.get('/api/0/test/issues?priority=high') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + for idx in range(len(data['issues'])): + data['issues'][idx]['date_created'] = '1431414800' + data['issues'][idx]['last_updated'] = '1431414800' + self.assertDictEqual( + data, + { + "args": { + "assignee": None, + "author": None, + 'milestones': [], + 'priority': 'high', "since": None, "status": None, "tags": [], @@ -1238,6 +1338,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": None, "status": None, "tags": [] @@ -1264,6 +1365,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": start, "status": None, "tags": [] @@ -1287,6 +1389,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": middle, "status": None, "tags": [] @@ -1310,6 +1413,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": final, "status": None, "tags": [] @@ -1336,6 +1440,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'priority': None, "since": final, "status": None, "tags": [] From ca5286e41ee078b8eccf8743905f46ca9c9e77b8 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 16 2017 11:14:26 +0000 Subject: [PATCH 4/7] Adjust documentation, keep the ordering consistent and small style changes --- diff --git a/pagure/api/issue.py b/pagure/api/issue.py index 08906ea..051a420 100644 --- a/pagure/api/issue.py +++ b/pagure/api/issue.py @@ -232,6 +232,12 @@ def api_view_issues(repo, username=None, namespace=None): | ``priority`` | string | Optional | | Filter the issues | | | | | by priority | +---------------+---------+--------------+---------------------------+ + | ``since`` | string | Optional | | Filter the issues | + | | | | updated after this date.| + | | | | The date can either be | + | | | | provided as an unix date| + | | | | or in the format Y-M-D | + +---------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ @@ -242,6 +248,9 @@ def api_view_issues(repo, username=None, namespace=None): "args": { "assignee": null, "author": null, + 'milestones': [], + 'priority': null, + "since": null, "status": "Closed", "tags": [ "0.1" @@ -283,14 +292,14 @@ def api_view_issues(repo, username=None, namespace=None): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) - status = flask.request.args.get('status', None) - tags = flask.request.args.getlist('tags') - tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) milestone = flask.request.args.getlist('milestones', None) priority = flask.request.args.get('priority', None) since = flask.request.args.get('since', None) + status = flask.request.args.get('status', None) + tags = flask.request.args.getlist('tags') + tags = [tag.strip() for tag in tags if tag.strip()] # Hide private tickets private = False @@ -347,13 +356,13 @@ def api_view_issues(repo, username=None, namespace=None): 'total_issues': len(issues), 'issues': [issue.to_json(public=True) for issue in issues], 'args': { - 'status': status, - 'tags': tags, 'assignee': assignee, 'author': author, - 'since': since 'milestones': milestone, 'priority': priority, + 'since': since, + 'status': status, + 'tags': tags, } }) return jsonout diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py index 184e327..323173d 100644 --- a/tests/test_pagure_flask_api_issue.py +++ b/tests/test_pagure_flask_api_issue.py @@ -830,7 +830,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): 'priority': None, "since": None, "status": None, - "tags": [] + "tags": [], }, "issues": FULL_ISSUE_LIST[3:], "total_issues": 6 From eb3bbceeafdb0177ee8fce0b70cc8b42fffe37ad Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 16 2017 11:14:26 +0000 Subject: [PATCH 5/7] Expand the API to support filtering issues having or not having a milestone Fixes https://pagure.io/pagure/issue/2105 --- diff --git a/pagure/api/issue.py b/pagure/api/issue.py index 051a420..474bbb7 100644 --- a/pagure/api/issue.py +++ b/pagure/api/issue.py @@ -232,6 +232,12 @@ def api_view_issues(repo, username=None, namespace=None): | ``priority`` | string | Optional | | Filter the issues | | | | | by priority | +---------------+---------+--------------+---------------------------+ + | ``no_stones`` | boolean | Optional | | If true returns only the| + | | | | issues having no | + | | | | milestone, if false | + | | | | returns only the issues | + | | | | having a milestone | + +---------------+---------+--------------+---------------------------+ | ``since`` | string | Optional | | Filter the issues | | | | | updated after this date.| | | | | The date can either be | @@ -249,6 +255,7 @@ def api_view_issues(repo, username=None, namespace=None): "assignee": null, "author": null, 'milestones': [], + 'no_stones': null, 'priority': null, "since": null, "status": "Closed", @@ -295,6 +302,12 @@ def api_view_issues(repo, username=None, namespace=None): assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) milestone = flask.request.args.getlist('milestones', None) + no_stones = flask.request.args.get('no_stones', None) + if no_stones is not None: + if str(no_stones).lower() in ['1', 'true', 't']: + no_stones = True + else: + no_stones = False priority = flask.request.args.get('priority', None) since = flask.request.args.get('since', None) status = flask.request.args.get('status', None) @@ -322,6 +335,7 @@ def api_view_issues(repo, username=None, namespace=None): 'private': private, 'milestones': milestone, 'priority': priority, + 'no_milestones': no_stones, } if status is not None: @@ -359,6 +373,7 @@ def api_view_issues(repo, username=None, namespace=None): 'assignee': assignee, 'author': author, 'milestones': milestone, + 'no_stones': no_stones, 'priority': priority, 'since': since, 'status': status, diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 0c555f7..c1875c2 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -2262,6 +2262,10 @@ def search_issues( query = query.filter( model.Issue.milestone.in_(milestones) ) + elif no_milestones is False: + query = query.filter( + model.Issue.milestone.isnot(None) + ) if custom_search: constraints = [] diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py index 323173d..944899d 100644 --- a/tests/test_pagure_flask_api_issue.py +++ b/tests/test_pagure_flask_api_issue.py @@ -827,6 +827,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -866,6 +867,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -907,6 +909,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -934,6 +937,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -975,6 +979,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -1001,6 +1006,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -1022,6 +1028,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": "Closed", @@ -1043,6 +1050,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": "Invalid", @@ -1067,6 +1075,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": "All", @@ -1132,6 +1141,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -1156,6 +1166,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': ['v1.0'], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -1221,6 +1232,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -1245,6 +1257,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': 'high', "since": None, "status": None, @@ -1255,6 +1268,122 @@ class PagureFlaskApiIssuetests(tests.Modeltests): } ) + def test_api_view_issues_no_stones(self): + """ Test the api_view_issues method of the flask api when filtering + with no_stones. + """ + tests.create_projects(self.session) + tests.create_projects_git( + os.path.join(self.path, 'tickets'), bare=True) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + repo = pagure.lib.get_project(self.session, 'test') + + # Create 2 tickets but only 1 has a milestone + start = datetime.datetime.utcnow().strftime('%s') + issue = pagure.lib.model.Issue( + id=pagure.lib.get_next_id(self.session, repo.id), + project_id=repo.id, + title='Issue #1', + content='Description', + user_id=1, # pingou + uid='issue#1', + private=False, + ) + self.session.add(issue) + self.session.commit() + + issue = pagure.lib.model.Issue( + id=pagure.lib.get_next_id(self.session, repo.id), + project_id=repo.id, + title='Issue #2', + content='Description', + user_id=1, # pingou + uid='issue#2', + private=False, + milestone='v1.0', + ) + self.session.add(issue) + self.session.commit() + + # List all opened issues + output = self.app.get('/api/0/test/issues') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + for idx in range(len(data['issues'])): + data['issues'][idx]['date_created'] = '1431414800' + data['issues'][idx]['last_updated'] = '1431414800' + lcl_issues = copy.deepcopy(LCL_ISSUES) + lcl_issues[0]['milestone'] = 'v1.0' + self.assertDictEqual( + data, + { + "args": { + "assignee": None, + "author": None, + 'milestones': [], + 'no_stones': None, + 'priority': None, + "since": None, + "status": None, + "tags": [], + }, + "issues": lcl_issues, + "total_issues": 2 + } + ) + + # List all issues with no milestone + output = self.app.get('/api/0/test/issues?no_stones=1') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + for idx in range(len(data['issues'])): + data['issues'][idx]['date_created'] = '1431414800' + data['issues'][idx]['last_updated'] = '1431414800' + self.assertDictEqual( + data, + { + "args": { + "assignee": None, + "author": None, + 'milestones': [], + 'no_stones': True, + 'priority': None, + "since": None, + "status": None, + "tags": [], + }, + "issues": [lcl_issues[1]], + "total_issues": 1 + } + ) + + # List all issues with a milestone + output = self.app.get('/api/0/test/issues?no_stones=0') + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + for idx in range(len(data['issues'])): + data['issues'][idx]['date_created'] = '1431414800' + data['issues'][idx]['last_updated'] = '1431414800' + self.assertDictEqual( + data, + { + "args": { + "assignee": None, + "author": None, + 'milestones': [], + 'no_stones': False, + 'priority': None, + "since": None, + "status": None, + "tags": [], + }, + "issues": [lcl_issues[0]], + "total_issues": 1 + } + ) + def test_api_view_issues_since(self): """ Test the api_view_issues method of the flask api for since option """ @@ -1338,6 +1467,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": None, "status": None, @@ -1365,6 +1495,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": start, "status": None, @@ -1389,6 +1520,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": middle, "status": None, @@ -1413,6 +1545,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": final, "status": None, @@ -1440,6 +1573,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "assignee": None, "author": None, 'milestones': [], + 'no_stones': None, 'priority': None, "since": final, "status": None, From 8278192acb4710874e6f9957e5412a4a776ff1a7 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 16 2017 11:14:26 +0000 Subject: [PATCH 6/7] Fix listing issues with no milestones in the UI We had to adjust the issue controller now that no_stone has three meaning: * True: all not assigned tickets to a milestone * False: all assigned tickets to a milestone * None: do not account for the presence of a milestone --- diff --git a/pagure/ui/issues.py b/pagure/ui/issues.py index cfc6e17..048264c 100644 --- a/pagure/ui/issues.py +++ b/pagure/ui/issues.py @@ -604,11 +604,10 @@ def view_issues(repo, username=None, namespace=None): for idx, key in enumerate(custom_keys): custom_search[key] = custom_values[idx] + no_stone = None if "none" in milestones: no_stone = True milestones.remove("none") - else: - no_stone = False search_string = search_pattern extra_fields, search_pattern = pagure.lib.tokenize_search_string( From 8232c56cb1a7a78f88c0236b0166d702a31d0323 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Mar 16 2017 13:26:46 +0000 Subject: [PATCH 7/7] Document the code a little more to explain what we try to do --- diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index c1875c2..4e15f14 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -2263,6 +2263,7 @@ def search_issues( model.Issue.milestone.in_(milestones) ) elif no_milestones is False: + # Asking for all ticket with a milestone query = query.filter( model.Issue.milestone.isnot(None) )