Commit f117c032 authored by Alexandru Croitor's avatar Alexandru Croitor

Add params validations and remove extra params support

Remove label_name and milestone_title params support
Add mutually_exclusive validation for author_id and author_username
Add mutually_exclusive validation for assignee_id and assignee_username
Add validation to allow single value for asignee_username on CE
Add separate issue_stats_params helper for statistics params and
reuse in issues_params.
parent a4fbf39e
...@@ -275,14 +275,6 @@ class IssuableFinder ...@@ -275,14 +275,6 @@ class IssuableFinder
params[:assignee_username].present? params[:assignee_username].present?
end end
def assignee_username
if params[:assignee_username].is_a?(Array)
params[:assignee_username].first
else
params[:assignee_username]
end
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def assignee def assignee
return @assignee if defined?(@assignee) return @assignee if defined?(@assignee)
...@@ -291,7 +283,7 @@ class IssuableFinder ...@@ -291,7 +283,7 @@ class IssuableFinder
if assignee_id? if assignee_id?
User.find_by(id: params[:assignee_id]) User.find_by(id: params[:assignee_id])
elsif assignee_username? elsif assignee_username?
User.find_by_username(assignee_username) User.find_by_username(params[:assignee_username])
else else
nil nil
end end
......
---
title: Add issues_statistics api endpoints and extend issues search api
merge_request: 27366
author:
type: added
...@@ -37,23 +37,26 @@ GET /issues?confidential=true ...@@ -37,23 +37,26 @@ GET /issues?confidential=true
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `state` | string | no | Return `all` issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `with_labels_data` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. | | `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search issues against their `title` and `description` | | `search` | string | no | Search issues against their `title` and `description` |
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` | | `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
| `created_after` | datetime | no | Return issues created on or after the given time | | `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created on or before the given time | | `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time | | `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time | | `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential ` | Boolean | no | Filter confidential or public issues. | | `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues
...@@ -109,7 +112,7 @@ Example response: ...@@ -109,7 +112,7 @@ Example response:
"title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.", "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
"created_at" : "2016-01-04T15:31:51.081Z", "created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6, "iid" : 6,
"labels" : [], "labels" : ["foo", "bar"],
"upvotes": 4, "upvotes": 4,
"downvotes": 0, "downvotes": 0,
"merge_requests_count": 0, "merge_requests_count": 0,
...@@ -122,8 +125,17 @@ Example response: ...@@ -122,8 +125,17 @@ Example response:
"human_time_estimate": null, "human_time_estimate": null,
"human_total_time_spent": null "human_total_time_spent": null
}, },
"has_tasks": true,
"task_status": "10 of 15 tasks completed",
"confidential": false, "confidential": false,
"discussion_locked": false "discussion_locked": false,
"_links":{
"self":"http://example.com/api/v4/projects/1/issues/76",
"notes":"`http://example.com/`api/v4/projects/1/issues/76/notes",
"award_emoji":"http://example.com/api/v4/projects/1/issues/76/award_emoji",
"project":"http://example.com/api/v4/projects/1"
},
"subscribed": false
} }
] ]
``` ```
...@@ -158,11 +170,14 @@ GET /groups/:id/issues?confidential=true ...@@ -158,11 +170,14 @@ GET /groups/:id/issues?confidential=true
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `with_labels_data` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. | | `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
...@@ -221,7 +236,7 @@ Example response: ...@@ -221,7 +236,7 @@ Example response:
"id" : 9, "id" : 9,
"name" : "Dr. Luella Kovacek" "name" : "Dr. Luella Kovacek"
}, },
"labels" : [], "labels" : ["foo", "bar"],
"upvotes": 4, "upvotes": 4,
"downvotes": 0, "downvotes": 0,
"merge_requests_count": 0, "merge_requests_count": 0,
...@@ -240,8 +255,17 @@ Example response: ...@@ -240,8 +255,17 @@ Example response:
"human_time_estimate": null, "human_time_estimate": null,
"human_total_time_spent": null "human_total_time_spent": null
}, },
"has_tasks": true,
"task_status": "10 of 15 tasks completed",
"confidential": false, "confidential": false,
"discussion_locked": false "discussion_locked": false,
"_links":{
"self":"http://example.com/api/v4/projects/4/issues/41",
"notes":"`http://example.com/`api/v4/projects/4/issues/41/notes",
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
"subscribed": false
} }
] ]
``` ```
...@@ -277,10 +301,13 @@ GET /projects/:id/issues?confidential=true ...@@ -277,10 +301,13 @@ GET /projects/:id/issues?confidential=true
| `iids[]` | Array[integer] | no | Return only the milestone having the given `iid` | | `iids[]` | Array[integer] | no | Return only the milestone having the given `iid` |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `with_labels_data` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. | | `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
...@@ -340,7 +367,7 @@ Example response: ...@@ -340,7 +367,7 @@ Example response:
"id" : 9, "id" : 9,
"name" : "Dr. Luella Kovacek" "name" : "Dr. Luella Kovacek"
}, },
"labels" : [], "labels" : ["foo", "bar"],
"upvotes": 4, "upvotes": 4,
"downvotes": 0, "downvotes": 0,
"merge_requests_count": 0, "merge_requests_count": 0,
...@@ -366,8 +393,17 @@ Example response: ...@@ -366,8 +393,17 @@ Example response:
"human_time_estimate": null, "human_time_estimate": null,
"human_total_time_spent": null "human_total_time_spent": null
}, },
"has_tasks": true,
"task_status": "10 of 15 tasks completed",
"confidential": false, "confidential": false,
"discussion_locked": false "discussion_locked": false,
"_links":{
"self":"http://example.com/api/v4/projects/4/issues/41",
"notes":"`http://example.com/`api/v4/projects/4/issues/41/notes",
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
"subscribed": false
} }
] ]
``` ```
......
# Issues Statistics API
Every API call to issues_statistics must be authenticated.
If a user is not a member of a project and the project is private, a `GET`
request on that project will result to a `404` status code.
## Get issues statistics
Gets issues count statistics on all issues the authenticated user has access to. By default it
returns only issues created by the current user. To get all issues,
use parameter `scope=all`.
```
GET /issues_statistics
GET /issues_statistics?labels=foo
GET /issues_statistics?labels=foo,bar
GET /issues_statistics?labels=foo,bar&state=opened
GET /issues_statistics?milestone=1.0.0
GET /issues_statistics?milestone=1.0.0&state=opened
GET /issues_statistics?iids[]=42&iids[]=43
GET /issues_statistics?author_id=5
GET /issues_statistics?assignee_id=5
GET /issues_statistics?my_reaction_emoji=star
GET /issues_statistics?search=foo&in=title
GET /issues_statistics?confidential=true
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
| `search` | string | no | Search issues against their `title` and `description` |
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues_statistics
```
Example response:
```json
{
"statistics": {
"counts": {
"all": 20,
"closed": 5,
"opened": 15
}
}
}
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
## List group issues
Get a list of a group's issues.
```
GET /groups/:id/issues_statistics
GET /groups/:id/issues_statistics?labels=foo
GET /groups/:id/issues_statistics?labels=foo,bar
GET /groups/:id/issues_statistics?labels=foo,bar&state=opened
GET /groups/:id/issues_statistics?milestone=1.0.0
GET /groups/:id/issues_statistics?milestone=1.0.0&state=opened
GET /groups/:id/issues_statistics?iids[]=42&iids[]=43
GET /groups/:id/issues_statistics?search=issue+title+or+description
GET /groups/:id/issues_statistics?author_id=5
GET /groups/:id/issues_statistics?assignee_id=5
GET /groups/:id/issues_statistics?my_reaction_emoji=star
GET /groups/:id/issues_statistics?confidential=true
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `search` | string | no | Search group issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues_statistics
```
Example response:
```json
{
"statistics": {
"counts": {
"all": 20,
"closed": 5,
"opened": 15
}
}
}
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
## List project issues
Get a list of a project's issues.
```
GET /projects/:id/issues_statistics
GET /projects/:id/issues_statistics?labels=foo
GET /projects/:id/issues_statistics?labels=foo,bar
GET /projects/:id/issues_statistics?labels=foo,bar&state=opened
GET /projects/:id/issues_statistics?milestone=1.0.0
GET /projects/:id/issues_statistics?milestone=1.0.0&state=opened
GET /projects/:id/issues_statistics?iids[]=42&iids[]=43
GET /projects/:id/issues_statistics?search=issue+title+or+description
GET /projects/:id/issues_statistics?author_id=5
GET /projects/:id/issues_statistics?assignee_id=5
GET /projects/:id/issues_statistics?my_reaction_emoji=star
GET /projects/:id/issues_statistics?confidential=true
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids[]` | Array[integer] | no | Return only the milestone having the given `iid` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `search` | string | no | Search project issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues_statistics
```
Example response:
```json
{
"statistics": {
"counts": {
"all": 20,
"closed": 5,
"opened": 15
}
}
}
```
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
...@@ -24,7 +24,6 @@ module API ...@@ -24,7 +24,6 @@ module API
args.delete(:id) args.delete(:id)
args[:milestone_title] ||= args.delete(:milestone) args[:milestone_title] ||= args.delete(:milestone)
args[:milestone_title] ||= args.delete(:milestone_title)
args[:label_name] ||= args.delete(:labels) args[:label_name] ||= args.delete(:labels)
args[:scope] = args[:scope].underscore if args[:scope] args[:scope] = args[:scope].underscore if args[:scope]
...@@ -35,10 +34,8 @@ module API ...@@ -35,10 +34,8 @@ module API
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
finder = issue_finder(args) finder = issue_finder(args)
issues = finder.execute.with_api_entity_associations issues = finder.execute.with_api_entity_associations
order_by = declared_params[:sort].present? && %w(asc desc).include?(declared_params[:sort].downcase)
issues = issues.reorder(order_options_with_tie_breaker) if order_by
issues issues.reorder(order_options_with_tie_breaker)
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
......
...@@ -19,15 +19,10 @@ module API ...@@ -19,15 +19,10 @@ module API
end end
end end
params :issues_params do params :issues_stats_params do
optional :labels, :label_name, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names' optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :with_labels_data, type: Boolean, desc: 'Return more label data than just lable title', default: false
optional :milestone, type: String, desc: 'Milestone title' optional :milestone, type: String, desc: 'Milestone title'
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', optional :milestone, type: String, desc: 'Return issues for a specific milestone'
desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
optional :milestone, :milestone_title, type: String, desc: 'Return issues for a specific milestone'
optional :iids, type: Array[Integer], desc: 'The IID array of issues' optional :iids, type: Array[Integer], desc: 'The IID array of issues'
optional :search, type: String, desc: 'Search issues for text present in the title, description, or any combination of these' optional :search, type: String, desc: 'Search issues for text present in the title, description, or any combination of these'
optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma' optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma'
...@@ -35,23 +30,39 @@ module API ...@@ -35,23 +30,39 @@ module API
optional :created_before, type: DateTime, desc: 'Return issues created before the specified time' optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time' optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time'
optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time' optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time'
optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID' optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
optional :author_username, type: String, desc: 'Return issues which are authored by the user with the given username' optional :author_username, type: String, desc: 'Return issues which are authored by the user with the given username'
mutually_exclusive :author_id, :author_username
optional :assignee_id, types: [Integer, String], integer_none_any: true, optional :assignee_id, types: [Integer, String], integer_none_any: true,
desc: 'Return issues which are assigned to the user with the given ID' desc: 'Return issues which are assigned to the user with the given ID'
optional :assignee_username, type: Array[String], optional :assignee_username, type: Array[String], check_assignees_count: true,
coerce_with: Validations::CheckAssigneesCount.coerce,
desc: 'Return issues which are assigned to the user with the given username' desc: 'Return issues which are assigned to the user with the given username'
mutually_exclusive :assignee_id, :assignee_username
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all],
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
optional :confidential, type: Boolean, desc: 'Filter confidential or public issues' optional :confidential, type: Boolean, desc: 'Filter confidential or public issues'
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
use :pagination
use :issues_params_ee if Gitlab.ee? use :issues_params_ee if Gitlab.ee?
end end
params :issues_params do
optional :with_labels_data, type: Boolean, desc: 'Return more label data than just lable title', default: false
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
use :issues_stats_params
use :pagination
end
params :issue_params do params :issue_params do
optional :description, type: String, desc: 'The description of an issue' optional :description, type: String, desc: 'The description of an issue'
optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue' optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue'
...@@ -68,7 +79,7 @@ module API ...@@ -68,7 +79,7 @@ module API
desc "Get currently authenticated user's issues statistics" desc "Get currently authenticated user's issues statistics"
params do params do
use :issues_params use :issues_stats_params
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me', optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me',
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
end end
...@@ -131,7 +142,7 @@ module API ...@@ -131,7 +142,7 @@ module API
desc 'Get statistics for the list of group issues' desc 'Get statistics for the list of group issues'
params do params do
use :issues_params use :issues_stats_params
end end
get ":id/issues_statistics" do get ":id/issues_statistics" do
group = find_group!(params[:id]) group = find_group!(params[:id])
...@@ -172,7 +183,7 @@ module API ...@@ -172,7 +183,7 @@ module API
desc 'Get statistics for the list of project issues' desc 'Get statistics for the list of project issues'
params do params do
use :issues_params use :issues_stats_params
end end
get ":id/issues_statistics" do get ":id/issues_statistics" do
project = find_project!(params[:id]) project = find_project!(params[:id])
......
# frozen_string_literal: true
module API
module Validations
class CheckAssigneesCount < Grape::Validations::Base
def self.coerce
lambda do |value|
case value
when String
[value]
when Array
value
when CheckAssigneesCount
value
else
[]
end
end
end
def validate_param!(attr_name, params)
unless param_allowed?(attr_name, params)
raise Grape::Exceptions::Validation,
params: [@scope.full_name(attr_name)],
message: "allows one value, but found #{params[attr_name].size}: #{params[attr_name].join(", ")}"
end
end
private
def param_allowed?(attr_name, params)
params[attr_name].size <= 1
end
end
end
end
...@@ -619,11 +619,33 @@ describe API::Issues do ...@@ -619,11 +619,33 @@ describe API::Issues do
let!(:issue2) { create(:issue, author: user2, project: group_project, created_at: 2.days.ago) } let!(:issue2) { create(:issue, author: user2, project: group_project, created_at: 2.days.ago) }
let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: group_project, created_at: 1.day.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: group_project, created_at: 1.day.ago) }
it 'returns issues with multiple assignees' do it 'returns issues with by assignee_username' do
get api("/groups/#{group.id}/issues", user), params: { assignee_username: [assignee.username, another_assignee.username] } get api(base_url, user), params: { assignee_username: [assignee.username], scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([issue3.id, group_confidential_issue.id]) expect_paginated_array_response([issue3.id, group_confidential_issue.id])
end end
it 'returns issues by assignee_username as string' do
get api(base_url, user), params: { assignee_username: assignee.username, scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([issue3.id, group_confidential_issue.id])
end
it 'returns error when multiple assignees are passed' do
get api(base_url, user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to include("allows one value, but found 2")
end
it 'returns error when assignee_username and assignee_id are passed together' do
get api(base_url, user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to include("mutually exclusive")
end
end end
end end
end end
......
...@@ -490,11 +490,33 @@ describe API::Issues do ...@@ -490,11 +490,33 @@ describe API::Issues do
let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) }
let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) }
it 'returns issues with multiple assignees' do it 'returns issues by assignee_username' do
get api("#{base_url}/issues", user), params: { assignee_username: [assignee.username, another_assignee.username] } get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([confidential_issue.id, issue3.id]) expect_paginated_array_response([confidential_issue.id, issue3.id])
end end
it 'returns issues by assignee_username as string' do
get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([confidential_issue.id, issue3.id])
end
it 'returns error when multiple assignees are passed' do
get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to include("allows one value, but found 2")
end
it 'returns error when assignee_username and assignee_id are passed together' do
get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to include("mutually exclusive")
end
end end
end end
......
...@@ -691,12 +691,33 @@ describe API::Issues do ...@@ -691,12 +691,33 @@ describe API::Issues do
let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) }
let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) }
it 'returns issues with multiple assignees' do it 'returns issues with by assignee_username' do
get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([confidential_issue.id, issue3.id])
end
it 'returns issues by assignee_username as string' do
get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' }
expect(issue3.reload.assignees).to eq([assignee, another_assignee]) expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([confidential_issue.id, issue3.id]) expect_paginated_array_response([confidential_issue.id, issue3.id])
end end
it 'returns error when multiple assignees are passed' do
get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to include("allows one value, but found 2")
end
it 'returns error when assignee_username and assignee_id are passed together' do
get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to include("mutually exclusive")
end
end end
end end
end end
......
...@@ -28,19 +28,7 @@ shared_examples 'labeled issues with labels and label_name params' do ...@@ -28,19 +28,7 @@ shared_examples 'labeled issues with labels and label_name params' do
it_behaves_like 'returns label names' it_behaves_like 'returns label names'
end end
context 'array of labeled issues when all labels match the label_name param' do context 'when with_labels_data provided' do
let(:params) { { label_name: "#{label.title},#{label_b.title},#{label_c.title}" } }
it_behaves_like 'returns label names'
end
context 'array of labeled issues when all labels match with label_name param as array' do
let(:params) { { label_name: [label.title, label_b.title, label_c.title] } }
it_behaves_like 'returns label names'
end
context 'with labels data' do
context 'array of labeled issues when all labels match' do context 'array of labeled issues when all labels match' do
let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_data: true } } let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_data: true } }
...@@ -52,17 +40,5 @@ shared_examples 'labeled issues with labels and label_name params' do ...@@ -52,17 +40,5 @@ shared_examples 'labeled issues with labels and label_name params' do
it_behaves_like 'returns basic label entity' it_behaves_like 'returns basic label entity'
end end
context 'array of labeled issues when all labels match the label_name param' do
let(:params) { { label_name: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_data: true } }
it_behaves_like 'returns basic label entity'
end
context 'array of labeled issues when all labels match with label_name param as array' do
let(:params) { { label_name: [label.title, label_b.title, label_c.title], with_labels_data: true } }
it_behaves_like 'returns basic label entity'
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment