Merge branch 'todos-api' into 'master'
Add Todos API * Closes #14068 * Closes #14675 - [x] Implementation - [x] Tests - [x] Documentation - [x] Changelog See merge request !3188
This commit is contained in:
commit
557ca2b31f
12 changed files with 970 additions and 1 deletions
|
@ -19,6 +19,7 @@ v 8.10.0 (unreleased)
|
|||
- Fix changing issue state columns in milestone view
|
||||
- Add notification settings dropdown for groups
|
||||
- Allow importing from Github using Personal Access Tokens. (Eric K Idema)
|
||||
- API: Todos !3188 (Robert Schilling)
|
||||
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
|
||||
- PipelinesFinder uses git cache data
|
||||
- Check for conflicts with existing Project's wiki path when creating a new project.
|
||||
|
|
|
@ -25,6 +25,7 @@ class TodosFinder
|
|||
def execute
|
||||
items = current_user.todos
|
||||
items = by_action_id(items)
|
||||
items = by_action(items)
|
||||
items = by_author(items)
|
||||
items = by_project(items)
|
||||
items = by_state(items)
|
||||
|
@ -43,6 +44,18 @@ class TodosFinder
|
|||
params[:action_id]
|
||||
end
|
||||
|
||||
def to_action_id
|
||||
Todo::ACTION_NAMES.key(action.to_sym)
|
||||
end
|
||||
|
||||
def action?
|
||||
action.present? && to_action_id
|
||||
end
|
||||
|
||||
def action
|
||||
params[:action]
|
||||
end
|
||||
|
||||
def author?
|
||||
params[:author_id].present?
|
||||
end
|
||||
|
@ -96,6 +109,14 @@ class TodosFinder
|
|||
params[:type]
|
||||
end
|
||||
|
||||
def by_action(items)
|
||||
if action?
|
||||
items = items.where(action: to_action_id)
|
||||
end
|
||||
|
||||
items
|
||||
end
|
||||
|
||||
def by_action_id(items)
|
||||
if action_id?
|
||||
items = items.where(action: action_id)
|
||||
|
|
|
@ -4,6 +4,13 @@ class Todo < ActiveRecord::Base
|
|||
BUILD_FAILED = 3
|
||||
MARKED = 4
|
||||
|
||||
ACTION_NAMES = {
|
||||
ASSIGNED => :assigned,
|
||||
MENTIONED => :mentioned,
|
||||
BUILD_FAILED => :build_failed,
|
||||
MARKED => :marked
|
||||
}
|
||||
|
||||
belongs_to :author, class_name: "User"
|
||||
belongs_to :note
|
||||
belongs_to :project
|
||||
|
@ -34,6 +41,10 @@ class Todo < ActiveRecord::Base
|
|||
action == BUILD_FAILED
|
||||
end
|
||||
|
||||
def action_name
|
||||
ACTION_NAMES[action]
|
||||
end
|
||||
|
||||
def body
|
||||
if note.present?
|
||||
note.note
|
||||
|
|
|
@ -36,6 +36,7 @@ following locations:
|
|||
- [System Hooks](system_hooks.md)
|
||||
- [Tags](tags.md)
|
||||
- [Users](users.md)
|
||||
- [Todos](todos.md)
|
||||
|
||||
### Internal CI API
|
||||
|
||||
|
|
|
@ -594,12 +594,103 @@ Example response:
|
|||
"id": 11,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
|
||||
"web_url": "http://lgitlab.example.com/u/orville"
|
||||
"web_url": "https://gitlab.example.com/u/orville"
|
||||
},
|
||||
"subscribed": false
|
||||
}
|
||||
```
|
||||
|
||||
## Create a todo
|
||||
|
||||
Manually creates a todo for the current user on an issue. If the request is
|
||||
successful, status code `200` together with the created todo is returned. If
|
||||
there already exists a todo for the user on that issue, status code `304` is
|
||||
returned.
|
||||
|
||||
```
|
||||
POST /projects/:id/issues/:issue_id/todo
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer | yes | The ID of a project |
|
||||
| `issue_id` | integer | yes | The ID of a project's issue |
|
||||
|
||||
```bash
|
||||
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/todo
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 112,
|
||||
"project": {
|
||||
"id": 5,
|
||||
"name": "Gitlab Ci",
|
||||
"name_with_namespace": "Gitlab Org / Gitlab Ci",
|
||||
"path": "gitlab-ci",
|
||||
"path_with_namespace": "gitlab-org/gitlab-ci"
|
||||
},
|
||||
"author": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"action_name": "marked",
|
||||
"target_type": "Issue",
|
||||
"target": {
|
||||
"id": 93,
|
||||
"iid": 10,
|
||||
"project_id": 5,
|
||||
"title": "Vel voluptas atque dicta mollitia adipisci qui at.",
|
||||
"description": "Tempora laboriosam sint magni sed voluptas similique.",
|
||||
"state": "closed",
|
||||
"created_at": "2016-06-17T07:47:39.486Z",
|
||||
"updated_at": "2016-07-01T11:09:13.998Z",
|
||||
"labels": [],
|
||||
"milestone": {
|
||||
"id": 26,
|
||||
"iid": 1,
|
||||
"project_id": 5,
|
||||
"title": "v0.0",
|
||||
"description": "Accusantium nostrum rerum quae quia quis nesciunt suscipit id.",
|
||||
"state": "closed",
|
||||
"created_at": "2016-06-17T07:47:33.832Z",
|
||||
"updated_at": "2016-06-17T07:47:33.832Z",
|
||||
"due_date": null
|
||||
},
|
||||
"assignee": {
|
||||
"name": "Jarret O'Keefe",
|
||||
"username": "francisca",
|
||||
"id": 14,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/francisca"
|
||||
},
|
||||
"author": {
|
||||
"name": "Maxie Medhurst",
|
||||
"username": "craig_rutherford",
|
||||
"id": 12,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/craig_rutherford"
|
||||
},
|
||||
"subscribed": true,
|
||||
"user_notes_count": 7,
|
||||
"upvotes": 0,
|
||||
"downvotes": 0
|
||||
},
|
||||
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
|
||||
"body": "Vel voluptas atque dicta mollitia adipisci qui at.",
|
||||
"state": "pending",
|
||||
"created_at": "2016-07-01T11:09:13.992Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Comments on issues
|
||||
|
||||
Comments are done via the [notes](notes.md) resource.
|
||||
|
|
|
@ -776,3 +776,101 @@ Example response:
|
|||
"subscribed": false
|
||||
}
|
||||
```
|
||||
|
||||
## Create a todo
|
||||
|
||||
Manually creates a todo for the current user on a merge request. If the
|
||||
request is successful, status code `200` together with the created todo is
|
||||
returned. If there already exists a todo for the user on that merge request,
|
||||
status code `304` is returned.
|
||||
|
||||
```
|
||||
POST /projects/:id/merge_requests/:merge_request_id/todo
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer | yes | The ID of a project |
|
||||
| `merge_request_id` | integer | yes | The ID of the merge request |
|
||||
|
||||
```bash
|
||||
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/27/todo
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 113,
|
||||
"project": {
|
||||
"id": 3,
|
||||
"name": "Gitlab Ci",
|
||||
"name_with_namespace": "Gitlab Org / Gitlab Ci",
|
||||
"path": "gitlab-ci",
|
||||
"path_with_namespace": "gitlab-org/gitlab-ci"
|
||||
},
|
||||
"author": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"action_name": "marked",
|
||||
"target_type": "MergeRequest",
|
||||
"target": {
|
||||
"id": 27,
|
||||
"iid": 7,
|
||||
"project_id": 3,
|
||||
"title": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.",
|
||||
"description": "Veniam sunt nihil modi earum cumque illum delectus. Nihil ad quis distinctio quia. Autem eligendi at quibusdam repellendus.",
|
||||
"state": "opened",
|
||||
"created_at": "2016-06-17T07:48:04.330Z",
|
||||
"updated_at": "2016-07-01T11:14:15.537Z",
|
||||
"target_branch": "allow_regex_for_project_skip_ref",
|
||||
"source_branch": "backup",
|
||||
"upvotes": 0,
|
||||
"downvotes": 0,
|
||||
"author": {
|
||||
"name": "Jarret O'Keefe",
|
||||
"username": "francisca",
|
||||
"id": 14,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/francisca"
|
||||
},
|
||||
"assignee": {
|
||||
"name": "Dr. Gabrielle Strosin",
|
||||
"username": "barrett.krajcik",
|
||||
"id": 4,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/733005fcd7e6df12d2d8580171ccb966?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/barrett.krajcik"
|
||||
},
|
||||
"source_project_id": 3,
|
||||
"target_project_id": 3,
|
||||
"labels": [],
|
||||
"work_in_progress": false,
|
||||
"milestone": {
|
||||
"id": 27,
|
||||
"iid": 2,
|
||||
"project_id": 3,
|
||||
"title": "v1.0",
|
||||
"description": "Quis ea accusantium animi hic fuga assumenda.",
|
||||
"state": "active",
|
||||
"created_at": "2016-06-17T07:47:33.840Z",
|
||||
"updated_at": "2016-06-17T07:47:33.840Z",
|
||||
"due_date": null
|
||||
},
|
||||
"merge_when_build_succeeds": false,
|
||||
"merge_status": "unchecked",
|
||||
"subscribed": true,
|
||||
"user_notes_count": 7
|
||||
},
|
||||
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7",
|
||||
"body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.",
|
||||
"state": "pending",
|
||||
"created_at": "2016-07-01T11:14:15.530Z"
|
||||
}
|
||||
```
|
||||
|
|
444
doc/api/todos.md
Normal file
444
doc/api/todos.md
Normal file
|
@ -0,0 +1,444 @@
|
|||
# Todos
|
||||
|
||||
**Note:** This feature was [introduced][ce-3188] in GitLab 8.10
|
||||
|
||||
## Get a list of todos
|
||||
|
||||
Returns a list of todos. When no filter is applied, it returns all pending todos
|
||||
for the current user. Different filters allow the user to precise the request.
|
||||
|
||||
```
|
||||
GET /todos
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, or `marked`. |
|
||||
| `author_id` | integer | no | The ID of an author |
|
||||
| `project_id` | integer | no | The ID of a project |
|
||||
| `state` | string | no | The state of the todo. Can be either `pending` or `done` |
|
||||
| `type` | string | no | The type of a todo. Can be either `Issue` or `MergeRequest` |
|
||||
|
||||
```bash
|
||||
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
|
||||
```
|
||||
|
||||
Example Response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 102,
|
||||
"project": {
|
||||
"id": 2,
|
||||
"name": "Gitlab Ce",
|
||||
"name_with_namespace": "Gitlab Org / Gitlab Ce",
|
||||
"path": "gitlab-ce",
|
||||
"path_with_namespace": "gitlab-org/gitlab-ce"
|
||||
},
|
||||
"author": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"action_name": "marked",
|
||||
"target_type": "MergeRequest",
|
||||
"target": {
|
||||
"id": 34,
|
||||
"iid": 7,
|
||||
"project_id": 2,
|
||||
"title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
|
||||
"state": "opened",
|
||||
"created_at": "2016-06-17T07:49:24.419Z",
|
||||
"updated_at": "2016-06-17T07:52:43.484Z",
|
||||
"target_branch": "tutorials_git_tricks",
|
||||
"source_branch": "DNSBL_docs",
|
||||
"upvotes": 0,
|
||||
"downvotes": 0,
|
||||
"author": {
|
||||
"name": "Maxie Medhurst",
|
||||
"username": "craig_rutherford",
|
||||
"id": 12,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/craig_rutherford"
|
||||
},
|
||||
"assignee": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"source_project_id": 2,
|
||||
"target_project_id": 2,
|
||||
"labels": [],
|
||||
"work_in_progress": false,
|
||||
"milestone": {
|
||||
"id": 32,
|
||||
"iid": 2,
|
||||
"project_id": 2,
|
||||
"title": "v1.0",
|
||||
"description": "Assumenda placeat ea voluptatem voluptate qui.",
|
||||
"state": "active",
|
||||
"created_at": "2016-06-17T07:47:34.163Z",
|
||||
"updated_at": "2016-06-17T07:47:34.163Z",
|
||||
"due_date": null
|
||||
},
|
||||
"merge_when_build_succeeds": false,
|
||||
"merge_status": "cannot_be_merged",
|
||||
"subscribed": true,
|
||||
"user_notes_count": 7
|
||||
},
|
||||
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
|
||||
"body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"state": "pending",
|
||||
"created_at": "2016-06-17T07:52:35.225Z"
|
||||
},
|
||||
{
|
||||
"id": 98,
|
||||
"project": {
|
||||
"id": 2,
|
||||
"name": "Gitlab Ce",
|
||||
"name_with_namespace": "Gitlab Org / Gitlab Ce",
|
||||
"path": "gitlab-ce",
|
||||
"path_with_namespace": "gitlab-org/gitlab-ce"
|
||||
},
|
||||
"author": {
|
||||
"name": "Maxie Medhurst",
|
||||
"username": "craig_rutherford",
|
||||
"id": 12,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/craig_rutherford"
|
||||
},
|
||||
"action_name": "assigned",
|
||||
"target_type": "MergeRequest",
|
||||
"target": {
|
||||
"id": 34,
|
||||
"iid": 7,
|
||||
"project_id": 2,
|
||||
"title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
|
||||
"state": "opened",
|
||||
"created_at": "2016-06-17T07:49:24.419Z",
|
||||
"updated_at": "2016-06-17T07:52:43.484Z",
|
||||
"target_branch": "tutorials_git_tricks",
|
||||
"source_branch": "DNSBL_docs",
|
||||
"upvotes": 0,
|
||||
"downvotes": 0,
|
||||
"author": {
|
||||
"name": "Maxie Medhurst",
|
||||
"username": "craig_rutherford",
|
||||
"id": 12,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/craig_rutherford"
|
||||
},
|
||||
"assignee": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"source_project_id": 2,
|
||||
"target_project_id": 2,
|
||||
"labels": [],
|
||||
"work_in_progress": false,
|
||||
"milestone": {
|
||||
"id": 32,
|
||||
"iid": 2,
|
||||
"project_id": 2,
|
||||
"title": "v1.0",
|
||||
"description": "Assumenda placeat ea voluptatem voluptate qui.",
|
||||
"state": "active",
|
||||
"created_at": "2016-06-17T07:47:34.163Z",
|
||||
"updated_at": "2016-06-17T07:47:34.163Z",
|
||||
"due_date": null
|
||||
},
|
||||
"merge_when_build_succeeds": false,
|
||||
"merge_status": "cannot_be_merged",
|
||||
"subscribed": true,
|
||||
"user_notes_count": 7
|
||||
},
|
||||
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
|
||||
"body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"state": "pending",
|
||||
"created_at": "2016-06-17T07:49:24.624Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Mark a todo as done
|
||||
|
||||
Marks a single pending todo given by its ID for the current user as done. The
|
||||
todo marked as done is returned in the response.
|
||||
|
||||
```
|
||||
DELETE /todos/:id
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer | yes | The ID of a todo |
|
||||
|
||||
```bash
|
||||
curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130
|
||||
```
|
||||
|
||||
Example Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 102,
|
||||
"project": {
|
||||
"id": 2,
|
||||
"name": "Gitlab Ce",
|
||||
"name_with_namespace": "Gitlab Org / Gitlab Ce",
|
||||
"path": "gitlab-ce",
|
||||
"path_with_namespace": "gitlab-org/gitlab-ce"
|
||||
},
|
||||
"author": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"action_name": "marked",
|
||||
"target_type": "MergeRequest",
|
||||
"target": {
|
||||
"id": 34,
|
||||
"iid": 7,
|
||||
"project_id": 2,
|
||||
"title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
|
||||
"state": "opened",
|
||||
"created_at": "2016-06-17T07:49:24.419Z",
|
||||
"updated_at": "2016-06-17T07:52:43.484Z",
|
||||
"target_branch": "tutorials_git_tricks",
|
||||
"source_branch": "DNSBL_docs",
|
||||
"upvotes": 0,
|
||||
"downvotes": 0,
|
||||
"author": {
|
||||
"name": "Maxie Medhurst",
|
||||
"username": "craig_rutherford",
|
||||
"id": 12,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/craig_rutherford"
|
||||
},
|
||||
"assignee": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"source_project_id": 2,
|
||||
"target_project_id": 2,
|
||||
"labels": [],
|
||||
"work_in_progress": false,
|
||||
"milestone": {
|
||||
"id": 32,
|
||||
"iid": 2,
|
||||
"project_id": 2,
|
||||
"title": "v1.0",
|
||||
"description": "Assumenda placeat ea voluptatem voluptate qui.",
|
||||
"state": "active",
|
||||
"created_at": "2016-06-17T07:47:34.163Z",
|
||||
"updated_at": "2016-06-17T07:47:34.163Z",
|
||||
"due_date": null
|
||||
},
|
||||
"merge_when_build_succeeds": false,
|
||||
"merge_status": "cannot_be_merged",
|
||||
"subscribed": true,
|
||||
"user_notes_count": 7
|
||||
},
|
||||
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
|
||||
"body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"state": "done",
|
||||
"created_at": "2016-06-17T07:52:35.225Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Mark all todos as done
|
||||
|
||||
Marks all pending todos for the current user as done. All todos marked as done
|
||||
are returned in the response.
|
||||
|
||||
```
|
||||
DELETE /todos
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
|
||||
```
|
||||
|
||||
Example Response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 102,
|
||||
"project": {
|
||||
"id": 2,
|
||||
"name": "Gitlab Ce",
|
||||
"name_with_namespace": "Gitlab Org / Gitlab Ce",
|
||||
"path": "gitlab-ce",
|
||||
"path_with_namespace": "gitlab-org/gitlab-ce"
|
||||
},
|
||||
"author": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"action_name": "marked",
|
||||
"target_type": "MergeRequest",
|
||||
"target": {
|
||||
"id": 34,
|
||||
"iid": 7,
|
||||
"project_id": 2,
|
||||
"title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
|
||||
"state": "opened",
|
||||
"created_at": "2016-06-17T07:49:24.419Z",
|
||||
"updated_at": "2016-06-17T07:52:43.484Z",
|
||||
"target_branch": "tutorials_git_tricks",
|
||||
"source_branch": "DNSBL_docs",
|
||||
"upvotes": 0,
|
||||
"downvotes": 0,
|
||||
"author": {
|
||||
"name": "Maxie Medhurst",
|
||||
"username": "craig_rutherford",
|
||||
"id": 12,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/craig_rutherford"
|
||||
},
|
||||
"assignee": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"source_project_id": 2,
|
||||
"target_project_id": 2,
|
||||
"labels": [],
|
||||
"work_in_progress": false,
|
||||
"milestone": {
|
||||
"id": 32,
|
||||
"iid": 2,
|
||||
"project_id": 2,
|
||||
"title": "v1.0",
|
||||
"description": "Assumenda placeat ea voluptatem voluptate qui.",
|
||||
"state": "active",
|
||||
"created_at": "2016-06-17T07:47:34.163Z",
|
||||
"updated_at": "2016-06-17T07:47:34.163Z",
|
||||
"due_date": null
|
||||
},
|
||||
"merge_when_build_succeeds": false,
|
||||
"merge_status": "cannot_be_merged",
|
||||
"subscribed": true,
|
||||
"user_notes_count": 7
|
||||
},
|
||||
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
|
||||
"body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"state": "done",
|
||||
"created_at": "2016-06-17T07:52:35.225Z"
|
||||
},
|
||||
{
|
||||
"id": 98,
|
||||
"project": {
|
||||
"id": 2,
|
||||
"name": "Gitlab Ce",
|
||||
"name_with_namespace": "Gitlab Org / Gitlab Ce",
|
||||
"path": "gitlab-ce",
|
||||
"path_with_namespace": "gitlab-org/gitlab-ce"
|
||||
},
|
||||
"author": {
|
||||
"name": "Maxie Medhurst",
|
||||
"username": "craig_rutherford",
|
||||
"id": 12,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/craig_rutherford"
|
||||
},
|
||||
"action_name": "assigned",
|
||||
"target_type": "MergeRequest",
|
||||
"target": {
|
||||
"id": 34,
|
||||
"iid": 7,
|
||||
"project_id": 2,
|
||||
"title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
|
||||
"state": "opened",
|
||||
"created_at": "2016-06-17T07:49:24.419Z",
|
||||
"updated_at": "2016-06-17T07:52:43.484Z",
|
||||
"target_branch": "tutorials_git_tricks",
|
||||
"source_branch": "DNSBL_docs",
|
||||
"upvotes": 0,
|
||||
"downvotes": 0,
|
||||
"author": {
|
||||
"name": "Maxie Medhurst",
|
||||
"username": "craig_rutherford",
|
||||
"id": 12,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/craig_rutherford"
|
||||
},
|
||||
"assignee": {
|
||||
"name": "Administrator",
|
||||
"username": "root",
|
||||
"id": 1,
|
||||
"state": "active",
|
||||
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
|
||||
"web_url": "https://gitlab.example.com/u/root"
|
||||
},
|
||||
"source_project_id": 2,
|
||||
"target_project_id": 2,
|
||||
"labels": [],
|
||||
"work_in_progress": false,
|
||||
"milestone": {
|
||||
"id": 32,
|
||||
"iid": 2,
|
||||
"project_id": 2,
|
||||
"title": "v1.0",
|
||||
"description": "Assumenda placeat ea voluptatem voluptate qui.",
|
||||
"state": "active",
|
||||
"created_at": "2016-06-17T07:47:34.163Z",
|
||||
"updated_at": "2016-06-17T07:47:34.163Z",
|
||||
"due_date": null
|
||||
},
|
||||
"merge_when_build_succeeds": false,
|
||||
"merge_status": "cannot_be_merged",
|
||||
"subscribed": true,
|
||||
"user_notes_count": 7
|
||||
},
|
||||
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
|
||||
"body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
|
||||
"state": "done",
|
||||
"created_at": "2016-06-17T07:49:24.624Z"
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188
|
|
@ -58,6 +58,7 @@ module API
|
|||
mount ::API::SystemHooks
|
||||
mount ::API::Tags
|
||||
mount ::API::Templates
|
||||
mount ::API::Todos
|
||||
mount ::API::Triggers
|
||||
mount ::API::Users
|
||||
mount ::API::Variables
|
||||
|
|
|
@ -272,6 +272,31 @@ module API
|
|||
expose :id, :project_id, :group_id, :group_access
|
||||
end
|
||||
|
||||
class Todo < Grape::Entity
|
||||
expose :id
|
||||
expose :project, using: Entities::BasicProjectDetails
|
||||
expose :author, using: Entities::UserBasic
|
||||
expose :action_name
|
||||
expose :target_type
|
||||
|
||||
expose :target do |todo, options|
|
||||
Entities.const_get(todo.target_type).represent(todo.target, options)
|
||||
end
|
||||
|
||||
expose :target_url do |todo, options|
|
||||
target_type = todo.target_type.underscore
|
||||
target_url = "namespace_project_#{target_type}_url"
|
||||
target_anchor = "note_#{todo.note_id}" if todo.note_id?
|
||||
|
||||
Gitlab::Application.routes.url_helpers.public_send(target_url,
|
||||
todo.project.namespace, todo.project, todo.target, anchor: target_anchor)
|
||||
end
|
||||
|
||||
expose :body
|
||||
expose :state
|
||||
expose :created_at
|
||||
end
|
||||
|
||||
class Namespace < Grape::Entity
|
||||
expose :id, :path, :kind
|
||||
end
|
||||
|
|
82
lib/api/todos.rb
Normal file
82
lib/api/todos.rb
Normal file
|
@ -0,0 +1,82 @@
|
|||
module API
|
||||
# Todos API
|
||||
class Todos < Grape::API
|
||||
before { authenticate! }
|
||||
|
||||
ISSUABLE_TYPES = {
|
||||
'merge_requests' => ->(id) { user_project.merge_requests.find(id) },
|
||||
'issues' => ->(id) { find_project_issue(id) }
|
||||
}
|
||||
|
||||
resource :projects do
|
||||
ISSUABLE_TYPES.each do |type, finder|
|
||||
type_id_str = "#{type.singularize}_id".to_sym
|
||||
|
||||
# Create a todo on an issuable
|
||||
#
|
||||
# Parameters:
|
||||
# id (required) - The ID of a project
|
||||
# issuable_id (required) - The ID of an issuable
|
||||
# Example Request:
|
||||
# POST /projects/:id/issues/:issuable_id/todo
|
||||
# POST /projects/:id/merge_requests/:issuable_id/todo
|
||||
post ":id/#{type}/:#{type_id_str}/todo" do
|
||||
issuable = instance_exec(params[type_id_str], &finder)
|
||||
todo = TodoService.new.mark_todo(issuable, current_user).first
|
||||
|
||||
if todo
|
||||
present todo, with: Entities::Todo, current_user: current_user
|
||||
else
|
||||
not_modified!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
resource :todos do
|
||||
helpers do
|
||||
def find_todos
|
||||
TodosFinder.new(current_user, params).execute
|
||||
end
|
||||
end
|
||||
|
||||
# Get a todo list
|
||||
#
|
||||
# Example Request:
|
||||
# GET /todos
|
||||
#
|
||||
get do
|
||||
todos = find_todos
|
||||
|
||||
present paginate(todos), with: Entities::Todo, current_user: current_user
|
||||
end
|
||||
|
||||
# Mark a todo as done
|
||||
#
|
||||
# Parameters:
|
||||
# id: (required) - The ID of the todo being marked as done
|
||||
#
|
||||
# Example Request:
|
||||
# DELETE /todos/:id
|
||||
#
|
||||
delete ':id' do
|
||||
todo = current_user.todos.find(params[:id])
|
||||
todo.done
|
||||
|
||||
present todo, with: Entities::Todo, current_user: current_user
|
||||
end
|
||||
|
||||
# Mark all todos as done
|
||||
#
|
||||
# Example Request:
|
||||
# DELETE /todos
|
||||
#
|
||||
delete do
|
||||
todos = find_todos
|
||||
todos.each(&:done)
|
||||
|
||||
present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo, current_user: current_user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,5 +22,9 @@ FactoryGirl.define do
|
|||
trait :build_failed do
|
||||
action { Todo::BUILD_FAILED }
|
||||
end
|
||||
|
||||
trait :done do
|
||||
state :done
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
190
spec/requests/api/todos_spec.rb
Normal file
190
spec/requests/api/todos_spec.rb
Normal file
|
@ -0,0 +1,190 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe API::Todos, api: true do
|
||||
include ApiHelpers
|
||||
|
||||
let(:project_1) { create(:project) }
|
||||
let(:project_2) { create(:project) }
|
||||
let(:author_1) { create(:user) }
|
||||
let(:author_2) { create(:user) }
|
||||
let(:john_doe) { create(:user, username: 'john_doe') }
|
||||
let(:merge_request) { create(:merge_request, source_project: project_1) }
|
||||
let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) }
|
||||
let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) }
|
||||
let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe) }
|
||||
let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) }
|
||||
|
||||
before do
|
||||
project_1.team << [john_doe, :developer]
|
||||
project_2.team << [john_doe, :developer]
|
||||
end
|
||||
|
||||
describe 'GET /todos' do
|
||||
context 'when unauthenticated' do
|
||||
it 'returns authentication error' do
|
||||
get api('/todos')
|
||||
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated' do
|
||||
it 'returns an array of pending todos for current user' do
|
||||
get api('/todos', john_doe)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(3)
|
||||
expect(json_response[0]['id']).to eq(pending_3.id)
|
||||
expect(json_response[0]['project']).to be_a Hash
|
||||
expect(json_response[0]['author']).to be_a Hash
|
||||
expect(json_response[0]['target_type']).to be_present
|
||||
expect(json_response[0]['target']).to be_a Hash
|
||||
expect(json_response[0]['target_url']).to be_present
|
||||
expect(json_response[0]['body']).to be_present
|
||||
expect(json_response[0]['state']).to eq('pending')
|
||||
expect(json_response[0]['action_name']).to eq('assigned')
|
||||
expect(json_response[0]['created_at']).to be_present
|
||||
end
|
||||
|
||||
context 'and using the author filter' do
|
||||
it 'filters based on author_id param' do
|
||||
get api('/todos', john_doe), { author_id: author_2.id }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and using the type filter' do
|
||||
it 'filters based on type param' do
|
||||
create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request)
|
||||
|
||||
get api('/todos', john_doe), { type: 'MergeRequest' }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and using the state filter' do
|
||||
it 'filters based on state param' do
|
||||
get api('/todos', john_doe), { state: 'done' }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and using the project filter' do
|
||||
it 'filters based on project_id param' do
|
||||
get api('/todos', john_doe), { project_id: project_2.id }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and using the action filter' do
|
||||
it 'filters based on action param' do
|
||||
get api('/todos', john_doe), { action: 'mentioned' }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /todos/:id' do
|
||||
context 'when unauthenticated' do
|
||||
it 'returns authentication error' do
|
||||
delete api("/todos/#{pending_1.id}")
|
||||
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated' do
|
||||
it 'marks a todo as done' do
|
||||
delete api("/todos/#{pending_1.id}", john_doe)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(pending_1.reload).to be_done
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /todos' do
|
||||
context 'when unauthenticated' do
|
||||
it 'returns authentication error' do
|
||||
delete api('/todos')
|
||||
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated' do
|
||||
it 'marks all todos as done' do
|
||||
delete api('/todos', john_doe)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(3)
|
||||
expect(pending_1.reload).to be_done
|
||||
expect(pending_2.reload).to be_done
|
||||
expect(pending_3.reload).to be_done
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'an issuable' do |issuable_type|
|
||||
it 'creates a todo on an issuable' do
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", john_doe)
|
||||
|
||||
expect(response.status).to eq(201)
|
||||
expect(json_response['project']).to be_a Hash
|
||||
expect(json_response['author']).to be_a Hash
|
||||
expect(json_response['target_type']).to eq(issuable.class.name)
|
||||
expect(json_response['target']).to be_a Hash
|
||||
expect(json_response['target_url']).to be_present
|
||||
expect(json_response['body']).to be_present
|
||||
expect(json_response['state']).to eq('pending')
|
||||
expect(json_response['action_name']).to eq('marked')
|
||||
expect(json_response['created_at']).to be_present
|
||||
end
|
||||
|
||||
it 'returns 304 there already exist a todo on that issuable' do
|
||||
create(:todo, project: project_1, author: author_1, user: john_doe, target: issuable)
|
||||
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", john_doe)
|
||||
|
||||
expect(response.status).to eq(304)
|
||||
end
|
||||
|
||||
it 'returns 404 if the issuable is not found' do
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/123/todo", john_doe)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST :id/issuable_type/:issueable_id/todo' do
|
||||
context 'for an issue' do
|
||||
it_behaves_like 'an issuable', 'issues' do
|
||||
let(:issuable) { create(:issue, author: author_1, project: project_1) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a merge request' do
|
||||
it_behaves_like 'an issuable', 'merge_requests' do
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue