Browse Source

Add API endpoint to retrieve all issues related to an user across all repos

Merges https://pagure.io/pagure/pull-request/2748
anar 6 năm trước cách đây
mục cha
commit
4bf12090eb
2 tập tin đã thay đổi với 239 bổ sung11 xóa
  1. 216 0
      pagure/api/user.py
  2. 23 11
      pagure/lib/__init__.py

+ 216 - 0
pagure/api/user.py

@@ -132,6 +132,222 @@ def api_view_user(username):
     return jsonout
 
 
+@API.route('/user/<username>/issues')
+@api_method
+def api_view_user_issues(username):
+    """
+    List user's issues
+    ---------------------
+    List issues opened by or assigned to a specific user across all projects.
+
+    ::
+
+        GET /api/0/user/<username>/issues
+
+    Parameters
+    ^^^^^^^^^^
+
+    +---------------+---------+--------------+---------------------------+
+    | Key           | Type    | Optionality  | Description               |
+    +===============+=========+==============+===========================+
+    | ``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`` |
+    +---------------+---------+--------------+---------------------------+
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+          "args": {
+            "milestones": [],
+            "no_stones": null,
+            "order": null,
+            "order_key": null,
+            "since": null,
+            "status": null,
+            "tags": []
+          },
+          "issues_assigned": [
+            {
+              "assignee": {
+                "fullname": "Anar Adilova",
+                "name": "anar"
+              },
+              "blocks": [],
+              "close_status": null,
+              "closed_at": null,
+              "comments": [],
+              "content": "Test Issue",
+              "custom_fields": [],
+              "date_created": "1510124763",
+              "depends": [],
+              "id": 2,
+              "last_updated": "1510124763",
+              "milestone": null,
+              "priority": null,
+              "private": false,
+              "status": "Open",
+              "tags": [],
+              "title": "issue4",
+              "user": {
+                "fullname": "Anar Adilova",
+                "name": "anar"
+              }
+            }
+          ],
+          "issues_created": [
+            {
+              "assignee": {
+                "fullname": "Anar Adilova",
+                "name": "anar"
+              },
+              "blocks": [],
+              "close_status": null,
+              "closed_at": null,
+              "comments": [],
+              "content": "Test Issue",
+              "custom_fields": [],
+              "date_created": "1510124763",
+              "depends": [],
+              "id": 2,
+              "last_updated": "1510124763",
+              "milestone": null,
+              "priority": null,
+              "private": false,
+              "status": "Open",
+              "tags": [],
+              "title": "issue4",
+              "user": {
+                "fullname": "Anar Adilova",
+                "name": "anar"
+              }
+            }
+          ],
+          "total_issues_assigned": 1,
+          "total_issues_created": 1
+        }
+
+
+    """
+    assignee = flask.request.args.get('assignee', None)
+    author = username
+    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
+    since = flask.request.args.get('since', None)
+    order = flask.request.args.get('order', None)
+    order_key = flask.request.args.get('order_key', None)
+    status = flask.request.args.get('status', None)
+    tags = flask.request.args.getlist('tags')
+    tags = [tag.strip() for tag in tags if tag.strip()]
+    params = {
+        'session': SESSION,
+        'tags': tags,
+        'milestones': milestone,
+        'order': order,
+        'order_key': order_key,
+        'no_milestones': no_stones,
+    }
+
+    if status is not None:
+        if status.lower() == 'all':
+            params.update({'status': None})
+        elif status.lower() == 'closed':
+            params.update({'closed': True})
+        else:
+            params.update({'status': status})
+    else:
+        params.update({'status': 'Open'})
+
+    updated_after = None
+    if since:
+        # Validate and convert the time
+        if since.isdigit():
+            # We assume its a timestamp, so convert it to datetime
+            try:
+                updated_after = datetime.datetime.fromtimestamp(int(since))
+            except ValueError:
+                raise pagure.exceptions.APIError(
+                    400, error_code=APIERROR.ETIMESTAMP)
+        else:
+            # We assume datetime format, so validate it
+            try:
+                updated_after = datetime.datetime.strptime(since, '%Y-%m-%d')
+            except ValueError:
+                raise pagure.exceptions.APIError(
+                    400, error_code=APIERROR.EDATETIME)
+
+    params.update({'updated_after': updated_after})
+    params_created = params.copy()
+    params_assigned = params
+    params.update({"author": username})
+    issues_created = pagure.lib.search_issues(**params_created)
+    params_assigned.update({"assignee": username})
+    issues_assigned = pagure.lib.search_issues(**params_assigned)
+    jsonout = flask.jsonify({
+        'total_issues_created': len(issues_created),
+        'total_issues_assigned': len(issues_assigned),
+        'issues_created': [issue.to_json(public=True)
+                           for issue in issues_created],
+        'issues_assigned': [issue.to_json(public=True)
+                            for issue in issues_assigned],
+        'args': {
+            'milestones': milestone,
+            'no_stones': no_stones,
+            'order': order,
+            'order_key': order_key,
+            'since': since,
+            'status': status,
+            'tags': tags,
+        }
+    })
+    return jsonout
+
+
 @API.route('/user/<username>/activity/stats')
 @api_method
 def api_view_user_activity_stats(username):

+ 23 - 11
pagure/lib/__init__.py

@@ -2189,7 +2189,7 @@ def _get_project(session, name, user=None, namespace=None, case=False):
 
 
 def search_issues(
-        session, repo, issueid=None, issueuid=None, status=None,
+        session, repo=None, issueid=None, issueuid=None, status=None,
         closed=False, tags=None, assignee=None, author=None, private=None,
         priority=None, milestones=None, count=False, offset=None,
         limit=None, search_pattern=None, custom_search=None,
@@ -2263,10 +2263,13 @@ def search_issues(
     '''
     query = session.query(
         sqlalchemy.distinct(model.Issue.uid)
-    ).filter(
-        model.Issue.project_id == repo.id
     )
 
+    if repo is not None:
+        query = query.filter(
+            model.Issue.project_id == repo.id
+        )
+
     if updated_after:
         query = query.filter(
             model.Issue.last_updated >= updated_after
@@ -2313,9 +2316,12 @@ def search_issues(
         if ytags:
             sub_q2 = session.query(
                 sqlalchemy.distinct(model.Issue.uid)
-            ).filter(
-                model.Issue.project_id == repo.id
-            ).filter(
+            )
+            if repo is not None:
+                sub_q2 = sub_q2.filter(
+                    model.Issue.project_id == repo.id
+                )
+            sub_q2 = sub_q2.filter(
                 model.Issue.uid == model.TagIssueColored.issue_uid
             ).filter(
                 model.TagIssueColored.tag_id == model.TagColored.id
@@ -2325,9 +2331,12 @@ def search_issues(
         if notags:
             sub_q3 = session.query(
                 sqlalchemy.distinct(model.Issue.uid)
-            ).filter(
-                model.Issue.project_id == repo.id
-            ).filter(
+            )
+            if repo is not None:
+                sub_q3 = sub_q3.filter(
+                    model.Issue.project_id == repo.id
+                )
+            sub_q3 = sub_q3.filter(
                 model.Issue.uid == model.TagIssueColored.issue_uid
             ).filter(
                 model.TagIssueColored.tag_id == model.TagColored.id
@@ -2462,10 +2471,13 @@ def search_issues(
         model.Issue
     ).filter(
         model.Issue.uid.in_(query.subquery())
-    ).filter(
-        model.Issue.project_id == repo.id
     )
 
+    if repo is not None:
+        query = query.filter(
+            model.Issue.project_id == repo.id
+        )
+
     if search_pattern is not None:
         query = query.filter(
             model.Issue.title.ilike('%%%s%%' % search_pattern)