diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee index 8eb005b0a22..12340bbce54 100644 --- a/app/assets/javascripts/right_sidebar.js.coffee +++ b/app/assets/javascripts/right_sidebar.js.coffee @@ -51,15 +51,19 @@ class @Sidebar $this = $(e.currentTarget) $todoLoading = $('.js-issuable-todo-loading') $btnText = $('.js-issuable-todo-text', $this) - ajaxType = if $this.attr('data-id') then 'PATCH' else 'POST' - ajaxUrlExtra = if $this.attr('data-id') then "/#{$this.attr('data-id')}" else '' + ajaxType = if $this.attr('data-delete-path') then 'DELETE' else 'POST' + + if $this.attr('data-delete-path') + url = "#{$this.attr('data-delete-path')}" + else + url = "#{$this.data('url')}" $.ajax( - url: "#{$this.data('url')}#{ajaxUrlExtra}" + url: url type: ajaxType dataType: 'json' data: - issuable_id: $this.data('issuable') + issuable_id: $this.data('issuable-id') issuable_type: $this.data('issuable-type') beforeSend: => @beforeTodoSend($this, $todoLoading) @@ -82,15 +86,15 @@ class @Sidebar else $todoPendingCount.removeClass 'hidden' - if data.todo? + if data.delete_path? $btn .attr 'aria-label', $btn.data('mark-text') - .attr 'data-id', data.todo.id + .attr 'data-delete-path', data.delete_path $btnText.text $btn.data('mark-text') else $btn .attr 'aria-label', $btn.data('todo-text') - .removeAttr 'data-id' + .removeAttr 'data-delete-path' $btnText.text $btn.data('todo-text') sidebarDropdownLoading: (e) -> diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb index 648d42c56c5..23868d986e9 100644 --- a/app/controllers/projects/todos_controller.rb +++ b/app/controllers/projects/todos_controller.rb @@ -1,18 +1,12 @@ class Projects::TodosController < Projects::ApplicationController + before_action :authenticate_user!, only: [:create] + def create - todos = TodoService.new.mark_todo(issuable, current_user) - - render json: { - todo: todos, - count: current_user.todos_pending_count, - } - end - - def update - current_user.todos.find_by_id(params[:id]).update(state: :done) + todo = TodoService.new.mark_todo(issuable, current_user) render json: { count: current_user.todos_pending_count, + delete_path: dashboard_todo_path(todo) } end @@ -22,7 +16,13 @@ class Projects::TodosController < Projects::ApplicationController @issuable ||= begin case params[:issuable_type] when "issue" - @project.issues.find(params[:issuable_id]) + issue = @project.issues.find(params[:issuable_id]) + + if can?(current_user, :read_issue, issue) + issue + else + render_404 + end when "merge_request" @project.merge_requests.find(params[:issuable_id]) end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8dbc51a689f..8231ce49fac 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -67,9 +67,9 @@ module IssuablesHelper end end - def has_todo(issuable) - unless current_user.nil? - current_user.todos.find_by(target_id: issuable.id, state: :pending) + def issuable_todo(issuable) + if current_user + current_user.todos.find_by(target: issuable, state: :pending) end end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index e1d8517f712..a832a6c8df7 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -12,7 +12,7 @@ module TodosHelper when Todo::ASSIGNED then 'assigned you' when Todo::MENTIONED then 'mentioned you on' when Todo::BUILD_FAILED then 'The build failed for your' - when Todo::MARKED then 'marked this as a Todo for' + when Todo::MARKED then 'added a todo for' end end diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 210c9b9aab5..adfab1af53e 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,4 +1,4 @@ -- todo = has_todo(issuable) +- todo = issuable_todo(issuable) %aside.right-sidebar{ class: sidebar_gutter_collapsed_class } .issuable-sidebar - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) @@ -9,12 +9,12 @@ %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } = sidebar_gutter_toggle_icon - if current_user - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add Todo" : "Mark Done") }, data: { todo_text: "Add Todo", mark_text: "Mark Done", id: (todo.id unless todo.nil?), issuable: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project) } } + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add Todo" : "Mark Done") }, data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } %span.js-issuable-todo-text - - if todo.nil? - Add Todo - - else + - if todo Mark Done + - else + Add Todo = icon('spin spinner', class: 'hidden js-issuable-todo-loading') = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| diff --git a/config/routes.rb b/config/routes.rb index 87da5e7178f..987d8507763 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -812,7 +812,7 @@ Rails.application.routes.draw do end end - resources :todos, only: [:create, :update], constraints: { id: /\d+/ } + resources :todos, only: [:create] resources :uploads, only: [:create] do collection do diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb new file mode 100644 index 00000000000..40a3403b660 --- /dev/null +++ b/spec/controllers/projects/todo_controller_spec.rb @@ -0,0 +1,102 @@ +require('spec_helper') + +describe Projects::TodosController do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, source_project: project) } + + context 'Issues' do + describe 'POST create' do + context 'when authorized' do + before do + sign_in(user) + project.team << [user, :developer] + end + + it 'should create todo for issue' do + expect do + post(:create, namespace_id: project.namespace.path, + project_id: project.path, + issuable_id: issue.id, + issuable_type: 'issue') + end.to change { user.todos.count }.by(1) + + expect(response.status).to eq(200) + end + end + + context 'when not authorized' do + it 'should not create todo for issue that user has no access to' do + sign_in(user) + expect do + post(:create, namespace_id: project.namespace.path, + project_id: project.path, + issuable_id: issue.id, + issuable_type: 'issue') + end.to change { user.todos.count }.by(0) + + expect(response.status).to eq(404) + end + + it 'should not create todo for issue when user not logged in' do + expect do + post(:create, namespace_id: project.namespace.path, + project_id: project.path, + issuable_id: issue.id, + issuable_type: 'issue') + end.to change { user.todos.count }.by(0) + + expect(response.status).to eq(302) + end + end + end + end + + context 'Merge Requests' do + describe 'POST create' do + context 'when authorized' do + before do + sign_in(user) + project.team << [user, :developer] + end + + it 'should create todo for merge request' do + expect do + post(:create, namespace_id: project.namespace.path, + project_id: project.path, + issuable_id: merge_request.id, + issuable_type: 'merge_request') + end.to change { user.todos.count }.by(1) + + expect(response.status).to eq(200) + end + end + + context 'when not authorized' do + it 'should not create todo for merge request user has no access to' do + sign_in(user) + expect do + post(:create, namespace_id: project.namespace.path, + project_id: project.path, + issuable_id: merge_request.id, + issuable_type: 'merge_request') + end.to change { user.todos.count }.by(0) + + expect(response.status).to eq(404) + end + + it 'should not create todo for merge request user has no access to' do + expect do + post(:create, namespace_id: project.namespace.path, + project_id: project.path, + issuable_id: merge_request.id, + issuable_type: 'merge_request') + end.to change { user.todos.count }.by(0) + + expect(response.status).to eq(302) + end + end + end + end +end diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index b69cce3e7d7..bc0f437a8ce 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -20,6 +20,12 @@ feature 'Manually create a todo item from issue', feature: true, js: true do page.within '.header-content .todos-pending-count' do expect(page).to have_content '1' end + + visit namespace_project_issue_path(project.namespace, project, issue) + + page.within '.header-content .todos-pending-count' do + expect(page).to have_content '1' + end end it 'should mark a todo as done' do @@ -29,5 +35,9 @@ feature 'Manually create a todo item from issue', feature: true, js: true do end expect(page).to have_selector('.todos-pending-count', visible: false) + + visit namespace_project_issue_path(project.namespace, project, issue) + + expect(page).to have_selector('.todos-pending-count', visible: false) end end