From 8728985293ee727653bebe95159fc497ae3410d1 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jan 08 2019 14:19:20 +0000 Subject: Add the possibility to filter the user's issues by dates This commit add support for filtering the issues returned by the ``/api/0/user//issues`` API endpoint by dates either one of: creation date, last update or close date. Fixes https://pagure.io/pagure/issue/4064 Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/api/user.py b/pagure/api/user.py index c9e1d9e..6b3d3e0 100644 --- a/pagure/api/user.py +++ b/pagure/api/user.py @@ -201,67 +201,98 @@ def api_view_user_issues(username): Parameters ^^^^^^^^^^ - +---------------+---------+--------------+---------------------------+ - | Key | Type | Optionality | Description | - +===============+=========+==============+===========================+ - | ``page`` | integer | Mandatory | | The page requested. | - | | | | Defaults to 1. | - +---------------+---------+--------------+---------------------------+ - | ``per_page`` | int | Optional | | The number of items | - | | | | to return per page. | - | | | | The maximum is 100. | - +---------------+---------+--------------+---------------------------+ - | ``status`` | string | Optional | | Filters the status of | - | | | | issues. Fetches all the | - | | | | issues if status is | - | | | | ``all``. Default: | - | | | | ``Open`` | - +---------------+---------+--------------+---------------------------+ - | ``tags`` | string | Optional | | A list of tags you | - | | | | wish to filter. If | - | | | | you want to filter | - | | | | for issues not having | - | | | | a tag, add an | - | | | | exclamation mark in | - | | | | front of it | - +---------------+---------+--------------+---------------------------+ - | ``milestones``| list of | Optional | | Filter the issues | - | | strings | | by milestone | - +---------------+---------+--------------+---------------------------+ - | ``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 | - | | | | provided as an unix date| - | | | | or in the format Y-M-D | - +---------------+---------+--------------+---------------------------+ - | ``order`` | string | Optional | | Set the ordering of the | - | | | | issues. This can be | - | | | | ``asc`` or ``desc``. | - | | | | Default: ``desc`` | - +---------------+---------+--------------+---------------------------+ - | ``order_key`` | string | Optional | | Set the ordering key. | - | | | | This can be ``assignee``| - | | | | , ``last_updated`` or | - | | | | name of other column. | - | | | | Default: | - | | | | ``date_created`` | - +---------------+---------+--------------+---------------------------+ - | ``assignee`` | boolean | Optional | | A boolean of whether to | - | | | | return the issues | - | | | | assigned to this user | - | | | | or not. Defaults to True| - +---------------+---------+--------------+---------------------------+ - | ``author`` | boolean | Optional | | A boolean of whether to | - | | | | return the issues | - | | | | created by this user or | - | | | | not. Defaults to True | - +---------------+---------+--------------+---------------------------+ + +---------------+---------+--------------+-----------------------------+ + | Key | Type | Optionality | Description | + +===============+=========+==============+=============================+ + | ``page`` | integer | Mandatory | | The page requested. | + | | | | Defaults to 1. | + +---------------+---------+--------------+-----------------------------+ + | ``per_page`` | int | Optional | | The number of items | + | | | | to return per page. | + | | | | The maximum is 100. | + +---------------+---------+--------------+-----------------------------+ + | ``status`` | string | Optional | | Filters the status of | + | | | | issues. Fetches all the | + | | | | issues if status is | + | | | | ``all``. Default: | + | | | | ``Open`` | + +---------------+---------+--------------+-----------------------------+ + | ``tags`` | string | Optional | | A list of tags you wish to| + | | | | filter. If you want to | + | | | | filter for issues not | + | | | | having a tag, add an | + | | | | exclamation mark in front | + | | | | of it | + +---------------+---------+--------------+-----------------------------+ + | ``milestones``| list of | Optional | | Filter the issues by | + | | strings | | milestone | + +---------------+---------+--------------+-----------------------------+ + | ``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 | + | | | | provided as an unix date | + | | | | or in the format Y-M-D | + +---------------+---------+--------------+-----------------------------+ + | ``order`` | string | Optional | | Set the ordering of the | + | | | | issues. This can be | + | | | | ``asc`` or ``desc``. | + | | | | Default: ``desc`` | + +---------------+---------+--------------+-----------------------------+ + | ``order_key`` | string | Optional | | Set the ordering key. | + | | | | This can be ``assignee`` | + | | | | , ``last_updated`` or | + | | | | name of other column. | + | | | | Default: ``date_created`` | + +---------------+---------+--------------+-----------------------------+ + | ``assignee`` | boolean | Optional | | A boolean of whether to | + | | | | return the issues | + | | | | assigned to this user | + | | | | or not. Defaults to True | + +---------------+---------+--------------+-----------------------------+ + | ``author`` | boolean | Optional | | A boolean of whether to | + | | | | return the issues | + | | | | created by this user or | + | | | | not. Defaults to True | + +---------------+---------+--------------+-----------------------------+ + | ``created`` | string | Optional | | Filter the issues returned| + | | | | by their creation date | + | | | | The date can be of | + | | | | specified either using | + | | | | a timestamp format or | + | | | | using the iso format for | + | | | | dates: yyyy-mm-dd. | + | | | | You can specify a start | + | | | | and a end date to this | + | | | | filter using start..end. | + +---------------+---------+--------------+-----------------------------+ + | ``updated`` | string | Optional | | Filter the pull-requests | + | | | | returned by their update | + | | | | date. The date can be of | + | | | | specified either using | + | | | | a timestamp format or | + | | | | using the iso format for | + | | | | dates: yyyy-mm-dd. | + | | | | You can specify a start | + | | | | and a end date to this | + | | | | filter using start..end. | + +---------------+---------+--------------+-----------------------------+ + | ``closed`` | string | Optional | | Filter the pull-requests | + | | | | returned by their closing | + | | | | date. The date can be of | + | | | | specified either using | + | | | | a timestamp format or | + | | | | using the iso format for | + | | | | dates: yyyy-mm-dd. | + | | | | You can specify a start | + | | | | and a end date to this | + | | | | filter using start..end. | + +---------------+---------+--------------+-----------------------------+ Sample response ^^^^^^^^^^^^^^^ @@ -279,7 +310,10 @@ def api_view_user_issues(username): "page": 1, "since": null, "status": null, - "tags": [] + "tags": [], + "created": null, + "updated": null, + "closed": null, }, "issues_assigned": [ { @@ -373,6 +407,18 @@ def api_view_user_issues(username): status = flask.request.args.get("status", None) tags = flask.request.args.getlist("tags") tags = [tag.strip() for tag in tags if tag.strip()] + created = flask.request.args.get("created") + updated = flask.request.args.get("updated") + closed = flask.request.args.get("closed") + + try: + created_since, created_until = validate_date_range(created) + updated_since, updated_until = validate_date_range(updated) + closed_since, closed_until = validate_date_range(closed) + except pagure.exceptions.InvalidTimestampException: + raise pagure.exceptions.APIError(400, error_code=APIERROR.ETIMESTAMP) + except pagure.exceptions.InvalidDateformatException: + raise pagure.exceptions.APIError(400, error_code=APIERROR.EDATETIME) page = get_page() per_page = get_per_page() @@ -400,6 +446,12 @@ def api_view_user_issues(username): "no_milestones": no_stones, "offset": offset, "limit": limit, + "created_since": created_since, + "created_until": created_until, + "updated_since": updated_since, + "updated_until": updated_until, + "closed_since": closed_since, + "closed_until": closed_until, } if status is not None: @@ -475,6 +527,9 @@ def api_view_user_issues(username): "page": page, "assignee": assignee, "author": author, + "created": created, + "updated": updated, + "closed": closed, }, } ) diff --git a/pagure/lib/query.py b/pagure/lib/query.py index 8ffb8fb..86bda50 100644 --- a/pagure/lib/query.py +++ b/pagure/lib/query.py @@ -2775,6 +2775,12 @@ def search_issues( custom_search=None, updated_after=None, no_milestones=None, + created_since=None, + created_until=None, + updated_since=None, + updated_until=None, + closed_since=None, + closed_until=None, order="desc", order_key=None, ): @@ -2990,6 +2996,21 @@ def search_issues( # Asking for all ticket with a milestone query = query.filter(model.Issue.milestone.isnot(None)) + if created_since: + query = query.filter(model.Issue.date_created >= created_since) + if created_until: + query = query.filter(model.Issue.date_created <= created_until) + + if updated_since: + query = query.filter(model.Issue.last_updated <= updated_since) + if updated_until: + query = query.filter(model.Issue.last_updated <= updated_until) + + if closed_since: + query = query.filter(model.Issue.closed_at <= closed_since) + if closed_until: + query = query.filter(model.Issue.closed_at <= closed_until) + if custom_search: constraints = [] for key in custom_search: diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py index 8be82a5..bc54cf2 100644 --- a/tests/test_pagure_flask_api_issue.py +++ b/tests/test_pagure_flask_api_issue.py @@ -4059,6 +4059,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): args = { "assignee": True, "author": True, + "closed": None, + "created": None, "milestones": [], "no_stones": None, "order": None, @@ -4066,7 +4068,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): "page": 1, "since": None, "status": None, - "tags": [] + "tags": [], + "updated": None, } self.assertEqual(data['args'], args) @@ -4084,6 +4087,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): args = { "assignee": True, "author": True, + "closed": None, + "created": None, "milestones": ['v1.0'], "no_stones": None, "order": None, @@ -4091,7 +4096,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): "page": 1, "since": None, "status": None, - "tags": [] + "tags": [], + "updated": None, } self.assertEqual(data['args'], args) @@ -4109,6 +4115,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): args = { "assignee": True, "author": True, + "closed": None, + "created": None, "milestones": [], "no_stones": None, "order": None, @@ -4116,7 +4124,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): "page": 1, "since": None, "status": 'closed', - "tags": [] + "tags": [], + "updated": None, } self.assertEqual(data['args'], args) @@ -4134,6 +4143,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): args = { "assignee": True, "author": True, + "closed": None, + "created": None, "milestones": [], "no_stones": None, "order": None, @@ -4141,7 +4152,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): "page": 1, "since": None, "status": 'all', - "tags": [] + "tags": [], + "updated": None, } self.assertEqual(data['args'], args) @@ -4177,6 +4189,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): args = { "assignee": True, "author": True, + "closed": None, + "created": None, "milestones": [], "no_stones": None, "order": None, @@ -4185,6 +4199,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): "since": None, "status": None, "tags": [], + "updated": None, } self.assertEqual(data['args'], args) @@ -4235,6 +4250,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): args = { "assignee": False, "author": True, + "closed": None, + "created": None, "milestones": [], "no_stones": None, "order": None, @@ -4243,6 +4260,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): "since": None, "status": None, "tags": [], + "updated": None, } self.assertEqual(data['args'], args) @@ -4264,6 +4282,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): args = { "assignee": True, "author": False, + "closed": None, + "created": None, "milestones": [], "no_stones": None, "order": None, @@ -4271,7 +4291,8 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest): "page": 1, "since": None, "status": None, - "tags": [] + "tags": [], + "updated": None, } self.assertEqual(data['args'], args) diff --git a/tests/test_pagure_flask_api_user.py b/tests/test_pagure_flask_api_user.py index 4f787d8..a705e81 100644 --- a/tests/test_pagure_flask_api_user.py +++ b/tests/test_pagure_flask_api_user.py @@ -1400,6 +1400,8 @@ class PagureFlaskApiUsertestissues(tests.Modeltests): "args": { "assignee": True, "author": True, + "closed": None, + "created": None, "milestones": [], "no_stones": None, "order": None, @@ -1407,7 +1409,8 @@ class PagureFlaskApiUsertestissues(tests.Modeltests): "page": 1, "since": None, "status": None, - "tags": [] + "tags": [], + "updated": None, }, "issues_assigned": [], "issues_created": [], @@ -1458,6 +1461,8 @@ class PagureFlaskApiUsertestissues(tests.Modeltests): "args": { "assignee": True, "author": True, + "closed": None, + "created": None, "milestones": [], "no_stones": None, "order": None, @@ -1465,7 +1470,8 @@ class PagureFlaskApiUsertestissues(tests.Modeltests): "page": 1, "since": None, "status": None, - "tags": [] + "tags": [], + "updated": None, }, "issues_assigned": [], "issues_created": [ @@ -1555,6 +1561,57 @@ class PagureFlaskApiUsertestissues(tests.Modeltests): } ) + def test_user_issues_created(self): + """ Return the list of issues associated with the specified user + and play with the created filter. """ + + today = datetime.datetime.utcnow().date() + output = self.app.get( + '/api/0/user/pingou/issues?created=%s' % (today.isoformat())) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data["total_issues_assigned"], 0) + self.assertEqual(data["total_issues_created"], 1) + + yesterday = today - datetime.timedelta(days=1) + output = self.app.get( + '/api/0/user/pingou/issues?created=%s' % (yesterday.isoformat())) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data["total_issues_assigned"], 0) + self.assertEqual(data["total_issues_created"], 1) + + tomorrow = today + datetime.timedelta(days=1) + output = self.app.get( + '/api/0/user/pingou/issues?created=%s' % (tomorrow.isoformat())) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data["total_issues_assigned"], 0) + self.assertEqual(data["total_issues_created"], 0) + + output = self.app.get( + '/api/0/user/pingou/issues?created=..%s' % (yesterday.isoformat())) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data["total_issues_assigned"], 0) + self.assertEqual(data["total_issues_created"], 0) + + output = self.app.get( + '/api/0/user/pingou/issues?created=%s..%s' % ( + yesterday.isoformat(), today.isoformat())) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data["total_issues_assigned"], 0) + self.assertEqual(data["total_issues_created"], 0) + + output = self.app.get( + '/api/0/user/pingou/issues?created=%s..%s' % ( + yesterday.isoformat(), tomorrow.isoformat())) + self.assertEqual(output.status_code, 200) + data = json.loads(output.get_data(as_text=True)) + self.assertEqual(data["total_issues_assigned"], 0) + self.assertEqual(data["total_issues_created"], 1) + if __name__ == '__main__': unittest.main(verbosity=2)