From a1f224d3f7b43bf2f58917e5045dcc2bdb33964d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 11 Mar 2016 16:04:42 -0300 Subject: Add Todos API --- app/models/todo.rb | 7 ++ app/views/dashboard/todos/_todo.html.haml | 2 +- lib/api/api.rb | 1 + lib/api/entities.rb | 29 ++++++++ lib/api/todos.rb | 54 +++++++++++++++ spec/factories/todos.rb | 4 ++ spec/requests/api/todos_spec.rb | 107 ++++++++++++++++++++++++++++++ 7 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 lib/api/todos.rb create mode 100644 spec/requests/api/todos_spec.rb diff --git a/app/models/todo.rb b/app/models/todo.rb index 2792fa9b9a8..42faecdf7f2 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -34,6 +34,13 @@ class Todo < ActiveRecord::Base action == BUILD_FAILED end + def action_name + case action + when Todo::ASSIGNED then 'assigned you' + when Todo::MENTIONED then 'mentioned you on' + end + end + def body if note.present? note.note diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 98f302d2f93..421885eef5b 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -11,7 +11,7 @@ - else (removed) %span.todo-label - = todo_action_name(todo) + = todo.action_name - if todo.target = todo_target_link(todo) - else diff --git a/lib/api/api.rb b/lib/api/api.rb index c3fff8b2f8f..3d7d67510a8 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -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 diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4e2a43e45e2..171a5da6420 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -56,6 +56,10 @@ module API expose :id expose :name, :name_with_namespace expose :path, :path_with_namespace + + expose :web_url do |project, options| + Gitlab::Application.routes.url_helpers.namespace_project_url(project.namespace, project) + end end class Project < Grape::Entity @@ -272,6 +276,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_id + expose :target_type + expose :target_reference do |todo, options| + todo.target.to_reference + 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.present? + + 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 diff --git a/lib/api/todos.rb b/lib/api/todos.rb new file mode 100644 index 00000000000..f45c0ae634a --- /dev/null +++ b/lib/api/todos.rb @@ -0,0 +1,54 @@ +module API + # Todos API + class Todos < Grape::API + before { authenticate! } + + 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 + @todos = paginate @todos + + present @todos, with: Entities::Todo + end + + # Mark 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 + end + + # Mark all todos as done + # + # Example Request: + # + # DELETE /todos + # + delete do + @todos = find_todos + @todos.each(&:done) + + present @todos, with: Entities::Todo + end + end + end +end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index f426e27afed..7fc20cd5555 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -22,5 +22,9 @@ FactoryGirl.define do trait :build_failed do action { Todo::BUILD_FAILED } end + + trait :done do + state :done + end end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb new file mode 100644 index 00000000000..20d57828674 --- /dev/null +++ b/spec/requests/api/todos_spec.rb @@ -0,0 +1,107 @@ +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, 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, target: merge_request) } + let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } + + describe 'GET /todos' do + context 'when unauthenticated' do + it 'should return authentication error' do + get api('/todos') + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'should return 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) + end + + context 'and using the author filter' do + it 'should filter 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 'should filter based on type param' do + 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 'should filter 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 'should filter based on project_id param' do + project_2.team << [john_doe, :developer] + 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 + end + end + + describe 'DELETE /todos/:id' do + context 'when unauthenticated' do + it 'should return authentication error' do + delete api("/todos/#{pending_1.id}") + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'should mark 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 'should return authentication error' do + delete api('/todos') + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'should mark all todos as done' do + delete api('/todos', john_doe) + expect(response.status).to eq(200) + expect(pending_1.reload).to be_done + expect(pending_2.reload).to be_done + expect(pending_3.reload).to be_done + end + end + end +end -- cgit v1.2.1 From 25dcd051372f6426b0ca5c73a4be6b8b075a21e7 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 18 May 2016 23:18:04 +0200 Subject: Fix rebase --- lib/api/entities.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 171a5da6420..67d2c396b32 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -56,10 +56,6 @@ module API expose :id expose :name, :name_with_namespace expose :path, :path_with_namespace - - expose :web_url do |project, options| - Gitlab::Application.routes.url_helpers.namespace_project_url(project.namespace, project) - end end class Project < Grape::Entity -- cgit v1.2.1 From b94088d5124b08349e5bd99c49a8ae3fcc5f5e6b Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 20 May 2016 23:17:13 +0200 Subject: Make tests follow the guidelines --- spec/requests/api/todos_spec.rb | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 20d57828674..7ad6d26c42f 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -16,23 +16,26 @@ describe API::Todos, api: true do describe 'GET /todos' do context 'when unauthenticated' do - it 'should return authentication error' do + it 'returns authentication error' do get api('/todos') + expect(response.status).to eq(401) end end context 'when authenticated' do - it 'should return an array of pending todos for current user' 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) end context 'and using the author filter' do - it 'should filter based on author_id param' 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) @@ -40,8 +43,9 @@ describe API::Todos, api: true do end context 'and using the type filter' do - it 'should filter based on type param' do + it 'filters based on type param' do 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) @@ -49,8 +53,9 @@ describe API::Todos, api: true do end context 'and using the state filter' do - it 'should filter based on state param' 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) @@ -58,9 +63,10 @@ describe API::Todos, api: true do end context 'and using the project filter' do - it 'should filter based on project_id param' do + it 'filters based on project_id param' do project_2.team << [john_doe, :developer] 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) @@ -71,15 +77,17 @@ describe API::Todos, api: true do describe 'DELETE /todos/:id' do context 'when unauthenticated' do - it 'should return authentication error' 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 'should mark a todo as done' 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 @@ -88,15 +96,17 @@ describe API::Todos, api: true do describe 'DELETE /todos' do context 'when unauthenticated' do - it 'should return authentication error' do + it 'returns authentication error' do delete api('/todos') + expect(response.status).to eq(401) end end context 'when authenticated' do - it 'should mark all todos as done' do + it 'marks all todos as done' do delete api('/todos', john_doe) + expect(response.status).to eq(200) expect(pending_1.reload).to be_done expect(pending_2.reload).to be_done -- cgit v1.2.1 From 39e6f504fcb8c6cab511a50cdefd76e821bfec17 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 May 2016 19:01:11 +0200 Subject: Move to helper, no instance variables --- app/models/todo.rb | 7 ------- app/views/dashboard/todos/_todo.html.haml | 2 +- lib/api/todos.rb | 18 +++++++++--------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index 42faecdf7f2..2792fa9b9a8 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -34,13 +34,6 @@ class Todo < ActiveRecord::Base action == BUILD_FAILED end - def action_name - case action - when Todo::ASSIGNED then 'assigned you' - when Todo::MENTIONED then 'mentioned you on' - end - end - def body if note.present? note.note diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 421885eef5b..98f302d2f93 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -11,7 +11,7 @@ - else (removed) %span.todo-label - = todo.action_name + = todo_action_name(todo) - if todo.target = todo_target_link(todo) - else diff --git a/lib/api/todos.rb b/lib/api/todos.rb index f45c0ae634a..db1f16cec59 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -14,11 +14,11 @@ module API # # Example Request: # GET /todos + # get do - @todos = find_todos - @todos = paginate @todos + todos = find_todos - present @todos, with: Entities::Todo + present paginate(todos), with: Entities::Todo end # Mark todo as done @@ -31,10 +31,10 @@ module API # DELETE /todos/:id # delete ':id' do - @todo = current_user.todos.find(params[:id]) - @todo.done + todo = current_user.todos.find(params[:id]) + todo.done - present @todo, with: Entities::Todo + present todo, with: Entities::Todo end # Mark all todos as done @@ -44,10 +44,10 @@ module API # DELETE /todos # delete do - @todos = find_todos - @todos.each(&:done) + todos = find_todos + todos.each(&:done) - present @todos, with: Entities::Todo + present paginate(todos), with: Entities::Todo end end end -- cgit v1.2.1 From f3abd18c9c7da9c53bea2f08c2326a15ba5948f3 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 10 Jun 2016 12:24:38 +0200 Subject: Add user to project to see todos --- lib/api/entities.rb | 2 +- spec/requests/api/todos_spec.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 67d2c396b32..88f7fc7ff6c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -276,7 +276,7 @@ module API expose :id expose :project, using: Entities::BasicProjectDetails expose :author, using: Entities::UserBasic - expose :action_name + #expose :action_name expose :target_id expose :target_type expose :target_reference do |todo, options| diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 7ad6d26c42f..147028ba1c9 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -14,6 +14,11 @@ describe API::Todos, api: true do let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } 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 -- cgit v1.2.1 From 69397d559f837cb55fd50d5d0459523854dcec06 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 10 Jun 2016 13:02:41 +0200 Subject: Assert response body --- spec/requests/api/todos_spec.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 147028ba1c9..1960db59c99 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -35,6 +35,16 @@ describe API::Todos, api: true do 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_id']).to be_present + expect(json_response[0]['target_type']).to be_present + expect(json_response[0]['target_reference']).to be_present + 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]['created_at']).to be_present end context 'and using the author filter' do @@ -69,7 +79,6 @@ describe API::Todos, api: true do context 'and using the project filter' do it 'filters based on project_id param' do - project_2.team << [john_doe, :developer] get api('/todos', john_doe), { project_id: project_2.id } expect(response.status).to eq(200) @@ -113,6 +122,8 @@ describe API::Todos, api: true 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 -- cgit v1.2.1 From 631765748ebff2307a078dc6d50ef8367f3c5ff0 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 15 Jun 2016 13:20:30 +0200 Subject: Expose action_name --- app/models/todo.rb | 11 +++++++++++ lib/api/entities.rb | 2 +- spec/requests/api/todos_spec.rb | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index 2792fa9b9a8..3ba67078d48 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -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 diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 88f7fc7ff6c..67d2c396b32 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -276,7 +276,7 @@ module API expose :id expose :project, using: Entities::BasicProjectDetails expose :author, using: Entities::UserBasic - #expose :action_name + expose :action_name expose :target_id expose :target_type expose :target_reference do |todo, options| diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 1960db59c99..8d54b4fd3d8 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -44,6 +44,7 @@ describe API::Todos, api: true do 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 -- cgit v1.2.1 From 40c685c510fff48e0dc6a49c61704e8244ec6034 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 15 Jun 2016 16:06:38 +0200 Subject: pass paginated array when deleting notes --- lib/api/todos.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index db1f16cec59..10fd2aac092 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -47,7 +47,7 @@ module API todos = find_todos todos.each(&:done) - present paginate(todos), with: Entities::Todo + present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo end end end -- cgit v1.2.1 From fd9cd5ae8cd2d2f5488635b264eb86d89d768d66 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 16 Jun 2016 11:12:42 +0200 Subject: Add todos API documentation and changelog --- CHANGELOG | 1 + doc/api/README.md | 1 + doc/api/todos.md | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/api/todos.rb | 4 +- 4 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 doc/api/todos.md diff --git a/CHANGELOG b/CHANGELOG index 4b754c2aba3..98d23ea6824 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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. diff --git a/doc/api/README.md b/doc/api/README.md index 288f7f9ee69..d1e6c54c521 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -36,6 +36,7 @@ following locations: - [System Hooks](system_hooks.md) - [Tags](tags.md) - [Users](users.md) +- [Todos](todos.md) ### Internal CI API diff --git a/doc/api/todos.md b/doc/api/todos.md new file mode 100644 index 00000000000..1d38e4acf13 --- /dev/null +++ b/doc/api/todos.md @@ -0,0 +1,217 @@ +# 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 + +``` +GET /todos +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `action_id` | integer | no | The ID of the action of the todo. See the table below for the ID mapping | +| `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 an todo. Can be either `Issue` or `MergeRequest` | + +| `action_id` | Action | +| ----------- | ------ | +| 1 | Issuable assigned | +| 2 | Mentioned in issuable | +| 3 | Build failed | +| 4 | Todo marked for you | + + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos +``` + +Example Response: + +```json +[ + { + "id": 130, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "assigned", + "target_id": 71, + "target_type": "Issue", + "target_reference": "#1", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", + "body": "At voluptas qui nulla soluta qui et.", + "state": "pending", + "created_at": "2016-05-20T20:52:00.626Z" + }, + { + "id": 129, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "mentioned", + "target_id": 79, + "target_type": "Issue", + "target_reference": "#9", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/9#note_959", + "body": "@root Fix this shit", + "state": "pending", + "created_at": "2016-05-20T20:51:51.503Z" + } +] +``` + +## Mark a todo as done + +Marks a single pending todo given by its ID for the current user as done. The to +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": 130, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "assigned", + "target_id": 71, + "target_type": "Issue", + "target_reference": "#1", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", + "body": "At voluptas qui nulla soluta qui et.", + "state": "done", + "created_at": "2016-05-20T20:52:00.626Z" +} +``` + +## 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": 130, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "assigned", + "target_id": 71, + "target_type": "Issue", + "target_reference": "#1", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", + "body": "At voluptas qui nulla soluta qui et.", + "state": "done", + "created_at": "2016-05-20T20:52:00.626Z" + }, + { + "id": 129, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "mentioned", + "target_id": 79, + "target_type": "Issue", + "target_reference": "#9", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/9#note_959", + "body": "@root Fix this shit", + "state": "done", + "created_at": "2016-05-20T20:51:51.503Z" + } +] +``` + +[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188 diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 10fd2aac092..67714a796c7 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -21,13 +21,12 @@ module API present paginate(todos), with: Entities::Todo end - # Mark todo as done + # 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 @@ -40,7 +39,6 @@ module API # Mark all todos as done # # Example Request: - # # DELETE /todos # delete do -- cgit v1.2.1 From 3942621329b20307c1676d60324c8f47ea1e1b37 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 22 Jun 2016 19:15:09 +0200 Subject: Expose target, filter by state as string --- app/finders/todos_finder.rb | 21 ++ doc/api/todos.md | 447 ++++++++++++++++++++++++++++++---------- lib/api/entities.rb | 8 +- lib/api/todos.rb | 6 +- spec/requests/api/todos_spec.rb | 15 +- 5 files changed, 377 insertions(+), 120 deletions(-) diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 58a00f88af7..7806d9e4cc5 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -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) diff --git a/doc/api/todos.md b/doc/api/todos.md index 1d38e4acf13..29e73664410 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -5,7 +5,7 @@ ## 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 +for the current user. Different filters allow the user to precise the request. ``` GET /todos @@ -15,19 +15,11 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `action_id` | integer | no | The ID of the action of the todo. See the table below for the ID mapping | +| `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 an todo. Can be either `Issue` or `MergeRequest` | - -| `action_id` | Action | -| ----------- | ------ | -| 1 | Issuable assigned | -| 2 | Mentioned in issuable | -| 3 | Build failed | -| 4 | Todo marked for you | - +| `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 @@ -38,64 +30,158 @@ Example Response: ```json [ { - "id": 130, + "id": 102, "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" }, "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, + "name": "Administrator", + "username": "root", + "id": 1, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" }, - "action_name": "assigned", - "target_id": 71, - "target_type": "Issue", - "target_reference": "#1", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", - "body": "At voluptas qui nulla soluta qui et.", + "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-05-20T20:52:00.626Z" + "created_at": "2016-06-17T07:52:35.225Z" }, { - "id": 129, + "id": 98, "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" }, "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" }, - "action_name": "mentioned", - "target_id": 79, - "target_type": "Issue", - "target_reference": "#9", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/9#note_959", - "body": "@root Fix this shit", + "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-05-20T20:51:51.503Z" + "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 to -marked as done is returned in the response. +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 @@ -115,30 +201,77 @@ Example Response: ```json { - "id": 130, - "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" - }, - "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" - }, - "action_name": "assigned", - "target_id": 71, - "target_type": "Issue", - "target_reference": "#1", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", - "body": "At voluptas qui nulla soluta qui et.", - "state": "done", - "created_at": "2016-05-20T20:52:00.626Z" + "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" } ``` @@ -160,57 +293,151 @@ Example Response: ```json [ { - "id": 130, + "id": 102, "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" }, "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, + "name": "Administrator", + "username": "root", + "id": 1, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" }, - "action_name": "assigned", - "target_id": 71, - "target_type": "Issue", - "target_reference": "#1", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", - "body": "At voluptas qui nulla soluta qui et.", + "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-05-20T20:52:00.626Z" + "created_at": "2016-06-17T07:52:35.225Z" }, { - "id": 129, + "id": 98, "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" }, "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" }, - "action_name": "mentioned", - "target_id": 79, - "target_type": "Issue", - "target_reference": "#9", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/9#note_959", - "body": "@root Fix this shit", + "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-05-20T20:51:51.503Z" - } + "created_at": "2016-06-17T07:49:24.624Z" + }, ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 67d2c396b32..8cc4368b5c2 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -277,16 +277,16 @@ module API expose :project, using: Entities::BasicProjectDetails expose :author, using: Entities::UserBasic expose :action_name - expose :target_id expose :target_type - expose :target_reference do |todo, options| - todo.target.to_reference + + 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.present? + 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) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 67714a796c7..8334baad1b9 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -18,7 +18,7 @@ module API get do todos = find_todos - present paginate(todos), with: Entities::Todo + present paginate(todos), with: Entities::Todo, current_user: current_user end # Mark a todo as done @@ -33,7 +33,7 @@ module API todo = current_user.todos.find(params[:id]) todo.done - present todo, with: Entities::Todo + present todo, with: Entities::Todo, current_user: current_user end # Mark all todos as done @@ -45,7 +45,7 @@ module API todos = find_todos todos.each(&:done) - present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo + present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo, current_user: current_user end end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 8d54b4fd3d8..f93f37e3591 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -9,7 +9,7 @@ describe API::Todos, api: true do 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, project: project_1, author: author_1, user: john_doe) } + 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, target: merge_request) } let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } @@ -38,9 +38,8 @@ describe API::Todos, api: true do 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_id']).to be_present expect(json_response[0]['target_type']).to be_present - expect(json_response[0]['target_reference']).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') @@ -87,6 +86,16 @@ describe API::Todos, api: true do 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 -- cgit v1.2.1 From 87ac9c9850d602fd18654498ab3fa005d2b85ac7 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 28 Jun 2016 18:04:44 +0200 Subject: Support creating a todo on issuables via API --- doc/api/issues.md | 93 +++++++++++++++++++++++++++++++++++++- doc/api/merge_requests.md | 98 +++++++++++++++++++++++++++++++++++++++++ lib/api/todos.rb | 30 +++++++++++++ spec/requests/api/todos_spec.rb | 49 ++++++++++++++++++++- 4 files changed, 268 insertions(+), 2 deletions(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index 708fc691f67..3ced787b23e 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -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. diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 2930f615fc1..f60b0d0ebc6 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -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" +} +``` diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 8334baad1b9..2a6bfa98ca4 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -3,6 +3,36 @@ module 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 diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index f93f37e3591..92a4fa216cd 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -11,7 +11,7 @@ describe API::Todos, api: true do 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, target: merge_request) } + 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 @@ -59,6 +59,8 @@ describe API::Todos, api: true do 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) @@ -140,4 +142,49 @@ describe API::Todos, api: true do 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 -- cgit v1.2.1