From 0a27c129bc1ccab09be9f5e71ed194b55394caaf Mon Sep 17 00:00:00 2001
From: Felipe Artur <felipefac@gmail.com>
Date: Tue, 1 Jun 2021 16:31:14 -0300
Subject: [PATCH] Mark pending todo as done when resolving design discussions

Mark todos of user that resolved a design discussion as done

Changelog: added
---
 app/finders/pending_todos_finder.rb           |  9 ++++
 app/models/todo.rb                            |  1 +
 app/services/discussions/resolve_service.rb   |  7 +++
 app/services/todo_service.rb                  |  2 +
 doc/user/project/issues/design_management.md  |  3 ++
 doc/user/todos.md                             |  1 +
 spec/finders/pending_todos_finder_spec.rb     | 28 +++++++++---
 spec/models/todo_spec.rb                      | 12 +++++
 .../discussions/resolve_service_spec.rb       | 45 +++++++++++++++++++
 9 files changed, 101 insertions(+), 7 deletions(-)

diff --git a/app/finders/pending_todos_finder.rb b/app/finders/pending_todos_finder.rb
index d79a2340379cf..509370b49a877 100644
--- a/app/finders/pending_todos_finder.rb
+++ b/app/finders/pending_todos_finder.rb
@@ -26,6 +26,7 @@ def execute
     todos = by_project(todos)
     todos = by_target_id(todos)
     todos = by_target_type(todos)
+    todos = by_discussion(todos)
     by_commit_id(todos)
   end
 
@@ -60,4 +61,12 @@ def by_commit_id(todos)
       todos
     end
   end
+
+  def by_discussion(todos)
+    if (discussion = params[:discussion])
+      todos.for_note(discussion.notes)
+    else
+      todos
+    end
+  end
 end
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 23685fb68e0dc..94a996038489b 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -61,6 +61,7 @@ class Todo < ApplicationRecord
   scope :for_author, -> (author) { where(author: author) }
   scope :for_user, -> (user) { where(user: user) }
   scope :for_project, -> (projects) { where(project: projects) }
+  scope :for_note, -> (notes) { where(note: notes) }
   scope :for_undeleted_projects, -> { joins(:project).merge(Project.without_deleted) }
   scope :for_group, -> (group) { where(group: group) }
   scope :for_type, -> (type) { where(target_type: type) }
diff --git a/app/services/discussions/resolve_service.rb b/app/services/discussions/resolve_service.rb
index 3b733023eaedb..baf14aa8a03f7 100644
--- a/app/services/discussions/resolve_service.rb
+++ b/app/services/discussions/resolve_service.rb
@@ -47,9 +47,16 @@ def resolve_discussion(discussion)
         MergeRequests::ResolvedDiscussionNotificationService.new(project: project, current_user: current_user).execute(merge_request)
       end
 
+      resolve_user_todos_for(discussion)
       SystemNoteService.discussion_continued_in_issue(discussion, project, current_user, follow_up_issue) if follow_up_issue
     end
 
+    def resolve_user_todos_for(discussion)
+      return unless discussion.for_design?
+
+      TodoService.new.resolve_todos_for_target(discussion, current_user)
+    end
+
     def first_discussion
       @first_discussion ||= discussions.first
     end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index fc6543a8efc97..71bb813f384f3 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -316,6 +316,8 @@ def attributes_for_target(target)
       attributes.merge!(target_id: nil, commit_id: target.id)
     elsif target.is_a?(Issue)
       attributes[:issue_type] = target.issue_type
+    elsif target.is_a?(Discussion)
+      attributes.merge!(target_type: nil, target_id: nil, discussion: target)
     end
 
     attributes
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index 8b67c7e03ce94..8c42a27b4719d 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -219,6 +219,9 @@ There are two ways to resolve/unresolve a Design thread:
 
   ![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
 
+Resolving a discussion thread will also mark any pending to-do related to notes
+inside the thread as done. This is applicable only for to-dos owned by the user triggering the action.
+
 Note that your resolved comment pins disappear from the Design to free up space for new discussions.
 However, if you need to revisit or find a resolved discussion, all of your resolved threads are
 available in the **Resolved Comment** area at the bottom of the right sidebar.
diff --git a/doc/user/todos.md b/doc/user/todos.md
index 695532abf9f80..4227f46dfa8eb 100644
--- a/doc/user/todos.md
+++ b/doc/user/todos.md
@@ -112,6 +112,7 @@ Actions that dismiss to-do items include:
 - Changing the milestone
 - Adding/removing a label
 - Commenting on the issue
+- Resolving a [design discussion thread](project/issues/design_management.md#resolve-design-threads)
 
 Your To-Do List is personal, and items are only marked as done if you take
 action. If you close the issue or merge request, your to-do item is marked as
diff --git a/spec/finders/pending_todos_finder_spec.rb b/spec/finders/pending_todos_finder_spec.rb
index b17915f0d5921..f317d8b1633ef 100644
--- a/spec/finders/pending_todos_finder_spec.rb
+++ b/spec/finders/pending_todos_finder_spec.rb
@@ -3,8 +3,11 @@
 require 'spec_helper'
 
 RSpec.describe PendingTodosFinder do
-  let(:user) { create(:user) }
-  let(:user2) { create(:user) }
+  let_it_be(:user) { create(:user) }
+  let_it_be(:user2) { create(:user) }
+  let_it_be(:issue) { create(:issue) }
+  let_it_be(:note) { create(:note) }
+
   let(:users) { [user, user2] }
 
   describe '#execute' do
@@ -30,20 +33,16 @@
     end
 
     it 'supports retrieving of todos for a specific todo target' do
-      issue = create(:issue)
-      note = create(:note)
       todo = create(:todo, :pending, user: user, target: issue)
 
       create(:todo, :pending, user: user, target: note)
 
-      todos = described_class.new(users, target_id: issue.id).execute
+      todos = described_class.new(users, target_id: issue.id, target_type: 'Issue').execute
 
       expect(todos).to eq([todo])
     end
 
     it 'supports retrieving of todos for a specific target type' do
-      issue = create(:issue)
-      note = create(:note)
       todo = create(:todo, :pending, user: user, target: issue)
 
       create(:todo, :pending, user: user, target: note)
@@ -61,5 +60,20 @@
 
       expect(todos).to eq([todo])
     end
+
+    it 'supports retrieving of todos for specific discussion' do
+      first_discussion_note = create(:discussion_note_on_issue, noteable: issue, project: issue.project)
+      note_2 = create(:note, discussion_id: first_discussion_note.discussion_id)
+      note_3 = create(:note, discussion_id: first_discussion_note.discussion_id)
+      todo1 = create(:todo, :pending, target: issue, note: note_2, user: note_2.author)
+      todo2 = create(:todo, :pending, target: issue, note: note_3, user: note_3.author)
+      create(:todo, :pending, note: note, user: user)
+      discussion = Discussion.lazy_find(first_discussion_note.discussion_id)
+      users = [note_2.author, note_3.author, user]
+
+      todos = described_class.new(users, discussion: discussion).execute
+
+      expect(todos).to contain_exactly(todo1, todo2)
+    end
   end
 end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index caa0a886abf35..651e2cf273f99 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -376,6 +376,18 @@
     end
   end
 
+  describe '.for_note' do
+    it 'returns todos that belongs to notes' do
+      note_1 = create(:note, noteable: issue, project: issue.project)
+      note_2 = create(:note, noteable: issue, project: issue.project)
+      todo_1 = create(:todo, note: note_1)
+      todo_2 = create(:todo, note: note_2)
+      create(:todo, note: create(:note))
+
+      expect(described_class.for_note([note_1, note_2])).to contain_exactly(todo_1, todo_2)
+    end
+  end
+
   describe '.group_by_user_id_and_state' do
     let_it_be(:user1) { create(:user) }
     let_it_be(:user2) { create(:user) }
diff --git a/spec/services/discussions/resolve_service_spec.rb b/spec/services/discussions/resolve_service_spec.rb
index 2e30455eb0a7c..24de1d90526cc 100644
--- a/spec/services/discussions/resolve_service_spec.rb
+++ b/spec/services/discussions/resolve_service_spec.rb
@@ -121,5 +121,50 @@
         service.execute
       end
     end
+
+    context 'when resolving a discussion' do
+      def resolve_discussion(discussion, user)
+        described_class.new(project, user, one_or_more_discussions: discussion).execute
+      end
+
+      context 'in a design' do
+        let_it_be(:design) { create(:design, :with_file, issue: create(:issue, project: project)) }
+        let_it_be(:user_1) { create(:user) }
+        let_it_be(:user_2) { create(:user) }
+        let_it_be(:discussion_1) { create(:diff_note_on_design, noteable: design, project: project, author: user_1).to_discussion }
+        let_it_be(:discussion_2) { create(:diff_note_on_design, noteable: design, project: project, author: user_2).to_discussion }
+
+        before do
+          project.add_developer(user_1)
+          project.add_developer(user_2)
+        end
+
+        context 'when user resolving discussion has open todos' do
+          let!(:user_1_todo_for_discussion_1) { create(:todo, :pending, user: user_1, target: design, note: discussion_1.notes.first, project: project) }
+          let!(:user_1_todo_2_for_discussion_1) { create(:todo, :pending, user: user_1, target: design, note: discussion_1.notes.first, project: project) }
+          let!(:user_1_todo_for_discussion_2) { create(:todo, :pending, user: user_1, target: design, note: discussion_2.notes.first, project: project) }
+          let!(:user_2_todo_for_discussion_1) { create(:todo, :pending, user: user_2, target: design, note: discussion_1.notes.first, project: project) }
+
+          it 'marks user todos for given discussion as done' do
+            resolve_discussion(discussion_1, user_1)
+
+            expect(user_1_todo_for_discussion_1.reload).to be_done
+            expect(user_1_todo_2_for_discussion_1.reload).to be_done
+            expect(user_1_todo_for_discussion_2.reload).to be_pending
+            expect(user_2_todo_for_discussion_1.reload).to be_pending
+          end
+        end
+      end
+
+      context 'in a merge request' do
+        let!(:user_todo_for_discussion) { create(:todo, :pending, user: user, target: merge_request, note: discussion.notes.first, project: project) }
+
+        it 'does not mark user todo as done' do
+          resolve_discussion(discussion, user)
+
+          expect(user_todo_for_discussion).to be_pending
+        end
+      end
+    end
   end
 end
-- 
GitLab