From a1f224d3f7b43bf2f58917e5045dcc2bdb33964d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 11 Mar 2016 16:04:42 -0300 Subject: [PATCH] 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