From 1942d99575ec486f650d6490f30e6d026be9b290 Mon Sep 17 00:00:00 2001
From: Jan Beckmann <king-jan1999@hotmail.de>
Date: Fri, 27 Sep 2024 02:38:49 +0000
Subject: [PATCH] Allow reviewer unassign via quick action at MR creation

When using MR templates, it can be useful to use quick
actions to unassign previously assigned reviewers.

Relates to https://gitlab.com/gitlab-org/gitlab/-/issues/329978

Changelog: fixed
---
 .../quick_actions/interpret_service_spec.rb   |  7 +++++++
 .../quick_actions/merge_request_actions.rb    | 15 ++++++++++++--
 .../quick_actions/interpret_service_spec.rb   | 20 +++++++++++++++++++
 3 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/ee/spec/services/quick_actions/interpret_service_spec.rb b/ee/spec/services/quick_actions/interpret_service_spec.rb
index c5f46330e45f..f0e82e4fa77e 100644
--- a/ee/spec/services/quick_actions/interpret_service_spec.rb
+++ b/ee/spec/services/quick_actions/interpret_service_spec.rb
@@ -198,6 +198,7 @@
     context 'unassign_reviewer command' do
       let(:content) { '/unassign_reviewer' }
       let(:merge_request) { create(:merge_request, source_project: project) }
+      let(:merge_request_not_persisted) { build(:merge_request, source_project: project) }
 
       context 'unassign_reviewer command with multiple assignees' do
         it 'unassigns both reviewers if content contains /unassign_reviewer @user @user1' do
@@ -232,6 +233,12 @@
 
               expect(updates[:reviewer_ids]).to match_array([developer2.id])
             end
+
+            it 'will correctly remove the reviewer even for non-persisted merge requests' do
+              _, updates, _ = service.execute(content, merge_request_not_persisted)
+
+              expect(updates[:reviewer_ids]).to match_array([developer2.id])
+            end
           end
         end
       end
diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb
index addd7f098a33..5f9dbf90c057 100644
--- a/lib/gitlab/quick_actions/merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/merge_request_actions.rb
@@ -351,8 +351,7 @@ module MergeRequestActions
         end
         types MergeRequest
         condition do
-          quick_action_target.persisted? &&
-            reviewers_to_remove?(@updates) &&
+          reviewers_to_remove?(@updates) &&
             current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
         end
         parse_params do |unassign_reviewer_param|
@@ -360,12 +359,24 @@ module MergeRequestActions
           extract_users(unassign_reviewer_param) if quick_action_target.allows_multiple_reviewers?
         end
         command :unassign_reviewer, :remove_reviewer do |users = nil|
+          current_reviewers = quick_action_target.reviewers
+          # if preceding commands have been executed already, we need to use the updated reviewer_ids
+          current_reviewers = User.find(@updates[:reviewer_ids]) if @updates[:reviewer_ids].present?
+
           if quick_action_target.allows_multiple_reviewers? && users&.any?
             @updates[:reviewer_ids] ||= quick_action_target.reviewers.map(&:id)
             @updates[:reviewer_ids] -= users.map(&:id)
           else
             @updates[:reviewer_ids] = []
           end
+
+          removed_reviewers = current_reviewers.select { |user| @updates[:reviewer_ids].exclude?(user.id) }
+          # only generate the message here if the change would not be traceable otherwise
+          # because all reviewers have been assigned and removed immediately
+          if removed_reviewers.present? && !reviewers_to_remove?(@updates)
+            @execution_message[:unassign_reviewer] = _("Removed %{reviewer_text} %{reviewer_references}.") %
+              { reviewer_text: 'reviewer'.pluralize(removed_reviewers.size), reviewer_references: removed_reviewers.map(&:to_reference).to_sentence }
+          end
         end
       end
 
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index e7503fbd19b8..715ca663c888 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -1200,6 +1200,26 @@
         it_behaves_like 'failed command', 'Could not apply unassign_reviewer command.'
       end
 
+      context 'with a not-yet-persisted merge request and a preceding assign_reviewer command' do
+        let(:content) do
+          <<-QUICKACTION
+/assign_reviewer #{developer.to_reference}
+/unassign_reviewer #{developer.to_reference}
+          QUICKACTION
+        end
+
+        let(:issuable) { build(:merge_request) }
+
+        it 'adds and then removes a single reviewer in a single step' do
+          _, updates, message = service.execute(content, issuable)
+          translated_string = _("Assigned %{developer_to_reference} as reviewer. Removed reviewer %{developer_to_reference}.")
+          formatted_message = format(translated_string, developer_to_reference: developer.to_reference.to_s)
+
+          expect(updates).to eq(reviewer_ids: [])
+          expect(message).to eq(formatted_message)
+        end
+      end
+
       context 'with anything after the command' do
         let(:content) { '/unassign_reviewer supercalifragilisticexpialidocious' }
 
-- 
GitLab