From 65ef323bb69480090ffb6a9f12a00ae2ff8bf73b Mon Sep 17 00:00:00 2001
From: Alexandru Croitor <acroitor@gitlab.com>
Date: Thu, 31 Oct 2024 23:29:07 +0000
Subject: [PATCH] Handle award emoji data moved when work item is moved

This handles the award emojis assigned to the original work item
being copied to the work item in the new destination.

This also changes the copying of assignees to happen in bulk
rather than one item at a time.

re https://gitlab.com/gitlab-org/gitlab/-/issues/339766
---
 app/models/award_emoji.rb                     |   1 +
 .../data_sync/widgets/award_emoji.rb          |  27 ++++-
 .../data_sync/widgets/award_emoji_spec.rb     | 101 ++++++++++++++++++
 ...eable_and_moveable_data_stared_examples.rb |  13 ++-
 4 files changed, 137 insertions(+), 5 deletions(-)
 create mode 100644 spec/services/work_items/data_sync/widgets/award_emoji_spec.rb

diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index 1eed0b6becea..d8faea9f885d 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -9,6 +9,7 @@ class AwardEmoji < ApplicationRecord
   include Participable
   include GhostUser
   include Importable
+  include EachBatch
 
   belongs_to :awardable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
   belongs_to :user
diff --git a/app/services/work_items/data_sync/widgets/award_emoji.rb b/app/services/work_items/data_sync/widgets/award_emoji.rb
index c84d6af0649b..8021c97d3b19 100644
--- a/app/services/work_items/data_sync/widgets/award_emoji.rb
+++ b/app/services/work_items/data_sync/widgets/award_emoji.rb
@@ -4,13 +4,32 @@ module WorkItems
   module DataSync
     module Widgets
       class AwardEmoji < Base
-        def after_save_commit
-          # copy emoji, e.g.
-          # AwardEmojis::CopyService.new(work_item, target_work_item).execute
+        def after_create
+          return unless params[:operation] == :move
+          return unless target_work_item.get_widget(:award_emoji)
+
+          work_item.award_emoji.each_batch(of: BATCH_SIZE) do |awards_batch|
+            ::AwardEmoji.insert_all(new_work_item_award_emoji(awards_batch))
+          end
         end
 
         def post_move_cleanup
-          # do it
+          work_item.award_emoji.each_batch(of: BATCH_SIZE) do |award_emoji_batch|
+            award_emoji_batch.delete_all
+          end
+        end
+
+        private
+
+        def new_work_item_award_emoji(awards_batch)
+          awards_batch.map do |award|
+            new_award = award.attributes
+
+            new_award.delete("id")
+            new_award['awardable_id'] = target_work_item.id
+
+            new_award
+          end
         end
       end
     end
diff --git a/spec/services/work_items/data_sync/widgets/award_emoji_spec.rb b/spec/services/work_items/data_sync/widgets/award_emoji_spec.rb
new file mode 100644
index 000000000000..077f65914eb2
--- /dev/null
+++ b/spec/services/work_items/data_sync/widgets/award_emoji_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::DataSync::Widgets::AwardEmoji, feature_category: :team_planning do
+  let_it_be(:current_user) { create(:user) }
+  let_it_be_with_reload(:work_item) { create(:work_item) }
+  let_it_be_with_reload(:thumbs_up) { create(:award_emoji, name: 'thumbsup', awardable: work_item) }
+  let_it_be_with_reload(:thumbs_down) { create(:award_emoji, name: 'thumbsdown', awardable: work_item) }
+
+  let_it_be(:target_work_item) { create(:work_item) }
+  let(:params) { { operation: :move } }
+
+  subject(:callback) do
+    described_class.new(
+      work_item: work_item, target_work_item: target_work_item, current_user: current_user, params: params
+    )
+  end
+
+  describe '#before_create' do
+    context 'when target work item has award_emoji widget' do
+      before do
+        allow(target_work_item).to receive(:get_widget).with(:award_emoji).and_return(true)
+      end
+
+      context 'when moving work item' do
+        it 'copies award_emoji from work_item to target_work_item' do
+          expect(callback).to receive(:new_work_item_award_emoji).and_call_original
+          expect(::AwardEmoji).to receive(:insert_all).and_call_original
+
+          expected_result = work_item.reload.award_emoji.order(user_id: :asc, name: :asc).pluck(:user_id, :name)
+          callback.after_create
+
+          emojis = target_work_item.reload.award_emoji.order(user_id: :asc, name: :asc).pluck(:user_id, :name)
+          expect(emojis).to match_array(expected_result)
+        end
+      end
+
+      context 'when cloning work item' do
+        let(:params) { { operation: :clone } }
+
+        it 'copies award_emoji from work_item to target_work_item' do
+          expect(callback).not_to receive(:new_work_item_award_emoji)
+          expect(::AwardEmoji).not_to receive(:insert_all)
+
+          callback.after_create
+
+          expect(target_work_item.reload.award_emoji).to be_empty
+        end
+      end
+    end
+
+    context 'when target work item does not have award_emoji widget' do
+      before do
+        target_work_item.reload
+        allow(target_work_item).to receive(:get_widget).with(:award_emoji).and_return(false)
+      end
+
+      it 'does not copy award_emoji' do
+        expect(callback).not_to receive(:new_work_item_award_emoji)
+        expect(::AwardEmoji).not_to receive(:insert_all)
+
+        callback.after_create
+
+        expect(target_work_item.reload.award_emoji).to be_empty
+      end
+    end
+  end
+
+  describe '#post_move_cleanup' do
+    it 'is defined and can be called' do
+      expect(work_item.award_emoji.count).to eq(2)
+      expect { callback.post_move_cleanup }.not_to raise_error
+    end
+
+    it 'removes original work item award_emoji' do
+      expect(work_item.award_emoji.count).to eq(2)
+
+      callback.post_move_cleanup
+
+      expect(work_item.award_emoji).to be_empty
+    end
+
+    context 'when cleanup data in batches' do
+      before do
+        stub_const("#{described_class}::BATCH_SIZE", 2)
+      end
+
+      it 'removes original work item award_emoji' do
+        create(:award_emoji, name: 'star', awardable: work_item)
+        create(:award_emoji, name: 'grinning', awardable: work_item)
+
+        expect(work_item.award_emoji.count).to eq(4)
+
+        callback.post_move_cleanup
+
+        expect(work_item.reload.award_emoji).to be_empty
+      end
+    end
+  end
+end
diff --git a/spec/support/shared_examples/services/work_items/data_sync/cloneable_and_moveable_data_stared_examples.rb b/spec/support/shared_examples/services/work_items/data_sync/cloneable_and_moveable_data_stared_examples.rb
index d8e5d58031b9..70cb8c7e0cc6 100644
--- a/spec/support/shared_examples/services/work_items/data_sync/cloneable_and_moveable_data_stared_examples.rb
+++ b/spec/support/shared_examples/services/work_items/data_sync/cloneable_and_moveable_data_stared_examples.rb
@@ -58,14 +58,25 @@ def work_item_assignees(work_item)
     work_item.reload.assignees
   end
 
+  def work_item_award_emoji(work_item)
+    work_item.reload.award_emoji.order(user_id: :asc, name: :asc).pluck(:user_id, :name)
+  end
+
   where(:widget_name, :eval_value, :before_lambda, :expected_data, :operations) do
-    :assignees | :work_item_assignees | -> { set_assignees } | ref(:assignees) | [ref(:move), ref(:clone)]
+    :assignees   | :work_item_assignees   | -> { set_assignees } | ref(:assignees)     | [ref(:move), ref(:clone)]
+    :award_emoji | :work_item_award_emoji | -> {}                | ref(:award_emojis)  | [ref(:move)]
   end
 
   with_them do
     context "with widget" do
       let(:move) { WorkItems::DataSync::MoveService }
       let(:clone) { WorkItems::DataSync::CloneService }
+      let!(:thumbs_ups) { create_list(:award_emoji, 2, name: 'thumbsup', awardable: original_work_item) }
+      let!(:thumbs_downs) { create_list(:award_emoji, 2, name: 'thumbsdown', awardable: original_work_item) }
+
+      let!(:award_emojis) do
+        original_work_item.reload.award_emoji.order(user_id: :asc, name: :asc).pluck(:user_id, :name)
+      end
 
       before do
         instance_exec(&before_lambda)
-- 
GitLab