From f133c608ff99b4598aa124611c23111e7f5b6f31 Mon Sep 17 00:00:00 2001 From: Nicolas Dular <ndular@gitlab.com> Date: Thu, 23 May 2024 14:33:10 +0200 Subject: [PATCH] Use attributes for syncing work item to epic Similar to syncing epics to work items, we now switch directly assigning the AR attributes to the legacy epic. This makes it easier because: 1. We don't need to build up the parameters in the callbacks 2. We always sync all the data instead of partially syncing them based on changed params. --- app/services/work_items/callbacks/base.rb | 1 - .../work_items/callbacks/description.rb | 5 - .../callbacks/start_and_due_date.rb | 5 - .../concerns/work_items/sync_as_epic.rb | 50 ++++----- .../services/ee/work_items/callbacks/base.rb | 15 --- .../ee/work_items/callbacks/description.rb | 21 ---- .../callbacks/start_and_due_date.rb | 20 ---- ee/app/services/work_items/callbacks/color.rb | 1 - .../work_items/callbacks/description_spec.rb | 100 ------------------ .../callbacks/start_and_due_date_spec.rb | 61 ----------- .../work_items/callbacks/color_spec.rb | 12 --- .../work_items/create_service_spec.rb | 12 +++ .../work_items/update_service_spec.rb | 14 ++- 13 files changed, 48 insertions(+), 269 deletions(-) delete mode 100644 ee/app/services/ee/work_items/callbacks/base.rb delete mode 100644 ee/app/services/ee/work_items/callbacks/description.rb delete mode 100644 ee/app/services/ee/work_items/callbacks/start_and_due_date.rb delete mode 100644 ee/spec/services/ee/work_items/callbacks/description_spec.rb delete mode 100644 ee/spec/services/ee/work_items/callbacks/start_and_due_date_spec.rb diff --git a/app/services/work_items/callbacks/base.rb b/app/services/work_items/callbacks/base.rb index 97f1f0e26e4a..c91e2b37d108 100644 --- a/app/services/work_items/callbacks/base.rb +++ b/app/services/work_items/callbacks/base.rb @@ -11,4 +11,3 @@ def raise_error(message) end end end -WorkItems::Callbacks::Base.prepend_mod diff --git a/app/services/work_items/callbacks/description.rb b/app/services/work_items/callbacks/description.rb index fc94caf558c0..4a8e4d717698 100644 --- a/app/services/work_items/callbacks/description.rb +++ b/app/services/work_items/callbacks/description.rb @@ -3,8 +3,6 @@ module WorkItems module Callbacks class Description < Base - include Gitlab::Utils::StrongMemoize - def after_initialize params[:description] = nil if excluded_in_new_type? @@ -19,9 +17,6 @@ def after_initialize def update_description? params.present? && params.key?(:description) && has_permission?(:update_work_item) end - strong_memoize_attr :update_description? end end end - -WorkItems::Callbacks::Description.prepend_mod diff --git a/app/services/work_items/callbacks/start_and_due_date.rb b/app/services/work_items/callbacks/start_and_due_date.rb index 713b8938a96f..76bfecce67f9 100644 --- a/app/services/work_items/callbacks/start_and_due_date.rb +++ b/app/services/work_items/callbacks/start_and_due_date.rb @@ -3,8 +3,6 @@ module WorkItems module Callbacks class StartAndDueDate < Base - include Gitlab::Utils::StrongMemoize - def before_update return work_item.assign_attributes({ start_date: nil, due_date: nil }) if excluded_in_new_type? @@ -18,9 +16,6 @@ def before_update def update_start_and_due_date? params.present? && has_permission?(:set_work_item_metadata) end - strong_memoize_attr :update_start_and_due_date? end end end - -WorkItems::Callbacks::StartAndDueDate.prepend_mod diff --git a/ee/app/services/concerns/work_items/sync_as_epic.rb b/ee/app/services/concerns/work_items/sync_as_epic.rb index c362c5430ae1..ecbaa1403909 100644 --- a/ee/app/services/concerns/work_items/sync_as_epic.rb +++ b/ee/app/services/concerns/work_items/sync_as_epic.rb @@ -6,6 +6,12 @@ module SyncAsEpic private + BASE_ATTRIBUTE_PARAMS = %i[ + iid author_id created_at updated_at title title_html description description_html + confidential state_id last_edited_by_id last_edited_at external_key updated_by_id + closed_at closed_by_id imported_from + ].freeze + def create_epic_for!(work_item) return true unless work_item.namespace.work_item_sync_to_epic_enabled? @@ -22,7 +28,8 @@ def update_epic_for!(work_item) return true unless epic return true unless epic.group.work_item_sync_to_epic_enabled? - epic.update!(update_params(work_item)) + epic.assign_attributes(update_params(work_item)) + epic.save!(touch: false) rescue StandardError => error handle_error!(:update, error, work_item) end @@ -30,11 +37,9 @@ def update_epic_for!(work_item) def create_params(work_item) epic_params = {} - epic_params[:author] = work_item.author epic_params[:group] = work_item.namespace epic_params[:issue_id] = work_item.id epic_params[:iid] = work_item.iid - epic_params[:created_at] = work_item.created_at parent_link = WorkItems::ParentLink.find_by_work_item_id(work_item.id) @@ -44,42 +49,33 @@ def create_params(work_item) end epic_params - .merge(callback_params) .merge(base_attributes_params(work_item)) + .merge(color_params(work_item)) + .merge(date_params(work_item)) end def update_params(work_item) - callback_params + {} .merge(base_attributes_params(work_item)) + .merge(color_params(work_item)) + .merge(date_params(work_item)) end def base_attributes_params(work_item) - base_params = {} - - if params.has_key?(:title) - base_params[:title] = params[:title] - base_params[:title_html] = work_item.title_html - end - - base_params[:confidential] = params[:confidential] if params.has_key?(:confidential) - base_params[:updated_by] = work_item.updated_by - base_params[:updated_at] = work_item.updated_at - base_params[:external_key] = params[:external_key] if params[:external_key] + BASE_ATTRIBUTE_PARAMS.index_with { |attr| work_item[attr] } + end - if work_item.edited? - base_params[:last_edited_at] = work_item.last_edited_at - base_params[:last_edited_by] = work_item.last_edited_by - end + def color_params(work_item) + return {} unless work_item.color - base_params + { color: work_item.color.color } end - def callback_params - callbacks.reduce({}) do |params, callback| - next params unless callback.synced_epic_params.present? - - params.merge!(callback.synced_epic_params) - end + def date_params(work_item) + { + start_date: work_item.start_date, + due_date: work_item.due_date + } end def handle_error!(action, error, work_item) diff --git a/ee/app/services/ee/work_items/callbacks/base.rb b/ee/app/services/ee/work_items/callbacks/base.rb deleted file mode 100644 index 1d71b9d9ad3d..000000000000 --- a/ee/app/services/ee/work_items/callbacks/base.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module EE - module WorkItems - module Callbacks - module Base - extend ::Gitlab::Utils::Override - - def synced_epic_params - @synced_epic_params ||= {} - end - end - end - end -end diff --git a/ee/app/services/ee/work_items/callbacks/description.rb b/ee/app/services/ee/work_items/callbacks/description.rb deleted file mode 100644 index ab92b2264411..000000000000 --- a/ee/app/services/ee/work_items/callbacks/description.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module EE - module WorkItems - module Callbacks - module Description - extend ::Gitlab::Utils::Override - - override :after_initialize - def after_initialize - super - - return unless update_description? - - synced_epic_params[:description] = params[:description] - synced_epic_params[:description_html] = work_item.description_html - end - end - end - end -end diff --git a/ee/app/services/ee/work_items/callbacks/start_and_due_date.rb b/ee/app/services/ee/work_items/callbacks/start_and_due_date.rb deleted file mode 100644 index 6093222876a8..000000000000 --- a/ee/app/services/ee/work_items/callbacks/start_and_due_date.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module EE - module WorkItems - module Callbacks - module StartAndDueDate - extend ::Gitlab::Utils::Override - - override :before_update - def before_update - super - return unless update_start_and_due_date? - - synced_epic_params[:start_date] = params[:start_date] - synced_epic_params[:due_date] = params[:due_date] - end - end - end - end -end diff --git a/ee/app/services/work_items/callbacks/color.rb b/ee/app/services/work_items/callbacks/color.rb index 27ccb7ba6f10..3bf3e6d0b7d9 100644 --- a/ee/app/services/work_items/callbacks/color.rb +++ b/ee/app/services/work_items/callbacks/color.rb @@ -33,7 +33,6 @@ def handle_color_change if color.valid? work_item.color = color - synced_epic_params[:color] = ::Gitlab::Color.of(params[:color]) else raise_error(color.errors.full_messages.join(', ')) end diff --git a/ee/spec/services/ee/work_items/callbacks/description_spec.rb b/ee/spec/services/ee/work_items/callbacks/description_spec.rb deleted file mode 100644 index 77ffaa76947b..000000000000 --- a/ee/spec/services/ee/work_items/callbacks/description_spec.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe WorkItems::Callbacks::Description, feature_category: :portfolio_management do - let_it_be(:random_user) { create(:user) } - let_it_be(:author) { create(:user) } - let_it_be(:project) { create(:project, :public) } - let_it_be(:guest) { create(:user, guest_of: project) } - let_it_be(:reporter) { create(:user, reporter_of: project) } - - let(:params) { { description: 'updated description' } } - let(:current_user) { author } - let(:work_item) do - create( - :work_item, - author: author, - project: project, - description: 'old description', - last_edited_at: Date.yesterday, - last_edited_by: random_user - ) - end - - describe '#after_initialize' do - let(:service) { described_class.new(issuable: work_item, current_user: current_user, params: params) } - - subject(:after_initialize_callback) { service.after_initialize } - - shared_examples 'sets synced_epic_params' do - it 'set the synced_epic_params' do - subject - - expect(service.synced_epic_params[:description]).to eq(params[:description]) - expect(service.synced_epic_params[:description_html]).to eq(work_item.description_html) - end - end - - shared_examples 'does not set synced_epic_params' do - it 'does not set the synced_epic_params' do - subject - - expect(service.synced_epic_params[:description]).to be_nil - expect(service.synced_epic_params[:description_html]).to be_nil - end - end - - context 'when user has permission to update description' do - context 'when user is work item author' do - let(:current_user) { author } - - it_behaves_like 'sets synced_epic_params' - end - - context 'when user is a project reporter' do - let(:current_user) { reporter } - - it_behaves_like 'sets synced_epic_params' - end - - context 'when description is nil' do - let(:current_user) { author } - let(:params) { { description: nil } } - - it_behaves_like 'sets synced_epic_params' - end - - context 'when description is empty' do - let(:current_user) { author } - let(:params) { { description: '' } } - - it_behaves_like 'sets synced_epic_params' - end - - context 'when description param is not present' do - let(:params) { {} } - - it_behaves_like 'does not set synced_epic_params' - end - end - - context 'when user does not have permission to update description' do - context 'when user is a project guest' do - let(:current_user) { guest } - - it_behaves_like 'does not set synced_epic_params' - end - - context 'with private project' do - let_it_be(:project) { create(:project) } - - context 'when user is work item author' do - let(:current_user) { author } - - it_behaves_like 'does not set synced_epic_params' - end - end - end - end -end diff --git a/ee/spec/services/ee/work_items/callbacks/start_and_due_date_spec.rb b/ee/spec/services/ee/work_items/callbacks/start_and_due_date_spec.rb deleted file mode 100644 index b48e7512f6ea..000000000000 --- a/ee/spec/services/ee/work_items/callbacks/start_and_due_date_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe WorkItems::Callbacks::StartAndDueDate, feature_category: :portfolio_management do - let_it_be(:project) { create(:project) } - let_it_be(:user) { create(:user, reporter_of: project) } - let_it_be_with_reload(:work_item) { create(:work_item, project: project) } - - let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Callbacks::StartAndDueDate) } } - - describe '#before_update_callback' do - let(:start_date) { Date.today } - let(:due_date) { 1.week.from_now.to_date } - let(:service) { described_class.new(issuable: work_item, current_user: user, params: params) } - - subject(:update_params) { service.before_update } - - shared_examples 'does not set synced_epic_params' do - it 'does not set synced_epic_params' do - update_params - - expect(service.synced_epic_params).to be_empty - end - end - - context 'when start and due date params are present' do - let(:params) { { start_date: Date.today, due_date: 1.week.from_now.to_date } } - - it 'correctly sets synced_epic_params' do - update_params - - expect(service.synced_epic_params[:start_date]).to eq(start_date) - expect(service.synced_epic_params[:due_date]).to eq(due_date) - end - - context "and user doesn't have permissions to update start and due date" do - let_it_be(:user) { create(:user) } - - it_behaves_like 'does not set synced_epic_params' - end - end - - context 'when date params are not present' do - let(:params) { {} } - - it_behaves_like 'does not set synced_epic_params' - end - - context 'when start_date and due_date are null' do - context 'when one of the two params is null' do - let(:params) { { start_date: nil, due_date: nil } } - - it 'sets only one date to null' do - expect(service.synced_epic_params[:start_date]).to eq(nil) - expect(service.synced_epic_params[:due_date]).to eq(nil) - end - end - end - end -end diff --git a/ee/spec/services/work_items/callbacks/color_spec.rb b/ee/spec/services/work_items/callbacks/color_spec.rb index a6e2daae7cb5..c07b43478167 100644 --- a/ee/spec/services/work_items/callbacks/color_spec.rb +++ b/ee/spec/services/work_items/callbacks/color_spec.rb @@ -23,24 +23,12 @@ def work_item_color .to not_change { work_item_color } .and not_change { work_item.updated_at } end - - it 'does not set synced_epic_params' do - subject - - expect(callback.synced_epic_params).to be_empty - end end shared_examples 'color is updated' do |color| it 'updates work item color value' do expect { subject }.to change { work_item_color }.to(color) end - - it 'sets synced_epic_params' do - subject - - expect(callback.synced_epic_params[:color].to_s).to eq(color) - end end shared_examples 'raises a WidgetError' do diff --git a/ee/spec/services/work_items/create_service_spec.rb b/ee/spec/services/work_items/create_service_spec.rb index 22ca1652d54d..35b5d75cb42e 100644 --- a/ee/spec/services/work_items/create_service_spec.rb +++ b/ee/spec/services/work_items/create_service_spec.rb @@ -157,6 +157,18 @@ it_behaves_like 'syncs all data from a work_item to an epic' + context 'when creating the epic with only title and description' do + let(:widget_params) do + { + description_widget: { + description: 'new description' + } + } + end + + it_behaves_like 'syncs all data from a work_item to an epic' + end + context 'when creating an epic work item' do it 'creates the epic with correct relative_position' do work_item = service_result.payload[:work_item] diff --git a/ee/spec/services/work_items/update_service_spec.rb b/ee/spec/services/work_items/update_service_spec.rb index e999e35855b0..c10ae6139efb 100644 --- a/ee/spec/services/work_items/update_service_spec.rb +++ b/ee/spec/services/work_items/update_service_spec.rb @@ -436,6 +436,18 @@ it_behaves_like 'syncs all data from a work_item to an epic' + context 'when only providing title and description' do + let(:widget_params) do + { + description_widget: { + description: 'new description' + } + } + end + + it_behaves_like 'syncs all data from a work_item to an epic' + end + it 'syncs the data to the epic', :aggregate_failures do update_work_item @@ -481,7 +493,7 @@ context 'when updating the epic fails' do before do allow_next_found_instance_of(Epic) do |epic| - allow(epic).to receive(:update!).and_raise(ActiveRecord::RecordInvalid.new) + allow(epic).to receive(:save!).and_raise(ActiveRecord::RecordInvalid.new) end end -- GitLab