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