diff --git a/app/events/work_items/work_item_updated_event.rb b/app/events/work_items/work_item_updated_event.rb index 42ad7c92ae9..8cd6b0d4c59 100644 --- a/app/events/work_items/work_item_updated_event.rb +++ b/app/events/work_items/work_item_updated_event.rb @@ -9,6 +9,7 @@ module WorkItems 'properties' => { 'id' => { 'type' => 'integer' }, 'namespace_id' => { 'type' => 'integer' }, + 'previous_work_item_parent_id' => { 'type' => 'integer' }, 'updated_attributes' => { 'type' => 'array', 'items' => { diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 058c35fc2b0..28fdd674aa4 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -197,7 +197,11 @@ class CommitStatus < Ci::ApplicationRecord commit_status.failure_reason = reason.failure_reason_enum commit_status.allow_failure = true if reason.force_allow_failure? - commit_status.exit_code = reason.exit_code if Feature.enabled?(:ci_retry_on_exit_codes, Feature.current_request) + # Windows exit codes can reach a max value of 32-bit unsigned integer + # We only allow a smallint for exit_code in the db, hence the added limit of 32767 + if reason.exit_code && Feature.enabled?(:ci_retry_on_exit_codes, Feature.current_request) + commit_status.exit_code = reason.exit_code % 32768 + end end before_transition [:skipped, :manual] => :created do |commit_status, transition| diff --git a/app/services/work_items/update_service.rb b/app/services/work_items/update_service.rb index 046e6ebea54..b3ad3597839 100644 --- a/app/services/work_items/update_service.rb +++ b/app/services/work_items/update_service.rb @@ -18,7 +18,6 @@ module WorkItems updated_work_item = super if updated_work_item.valid? - publish_event(work_item) success(payload(work_item)) else error(updated_work_item.errors.full_messages, :unprocessable_entity, pass_back: payload(updated_work_item)) @@ -57,6 +56,13 @@ module WorkItems super end + override :associations_before_update + def associations_before_update(work_item) + super.merge( + work_item_parent_id: work_item.work_item_parent&.id + ) + end + def transaction_update(work_item, opts = {}) execute_widgets(work_item: work_item, callback: :before_update_in_transaction, widget_params: @widget_params) @@ -68,6 +74,7 @@ module WorkItems super GraphqlTriggers.issuable_title_updated(work_item) if work_item.previous_changes.key?(:title) + publish_event(work_item, old_associations) end def payload(work_item) @@ -82,10 +89,11 @@ module WorkItems ) end - def publish_event(work_item) + def publish_event(work_item, old_associations) event = WorkItems::WorkItemUpdatedEvent.new(data: { id: work_item.id, namespace_id: work_item.namespace_id, + previous_work_item_parent_id: old_associations[:work_item_parent_id], updated_attributes: work_item.previous_changes&.keys&.map(&:to_s), updated_widgets: @widget_params&.keys&.map(&:to_s) }.tap(&:compact_blank!)) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index f5f29109c10..72b59d84821 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -5257,6 +5257,15 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end end end + + context 'when exit code is greater than 32767' do + let(:exit_code) { 32770 } + + it 'wraps around to max size of a signed smallint' do + expect { drop_with_exit_code } + .to change { build.reload.metadata&.exit_code }.from(nil).to(2) + end + end end describe '#exit_codes_defined?' do diff --git a/spec/services/work_items/update_service_spec.rb b/spec/services/work_items/update_service_spec.rb index 739b3668fcc..a399804549c 100644 --- a/spec/services/work_items/update_service_spec.rb +++ b/spec/services/work_items/update_service_spec.rb @@ -39,7 +39,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do end shared_examples 'publish WorkItems::WorkItemUpdatedEvent event' do |attributes: nil, widgets: nil| - it do + specify do expect { expect(update_work_item[:status]).to eq(:success) } .to publish_event(WorkItems::WorkItemUpdatedEvent) .with({ @@ -51,18 +51,18 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do end end - shared_examples 'do not publish WorkItems::WorkItemUpdatedEvent event' do - it do + shared_examples 'does not publish WorkItems::WorkItemUpdatedEvent event' do + specify do expect { update_work_item }.not_to publish_event(WorkItems::WorkItemUpdatedEvent) end end - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event' + include_examples 'does not publish WorkItems::WorkItemUpdatedEvent event' context 'when applying quick actions' do let(:opts) { { description: "/shrug" } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ description description_html @@ -100,7 +100,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do context 'when title is changed' do let(:opts) { { title: 'changed' } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ lock_version title @@ -132,7 +132,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do context 'when title is not changed' do let(:opts) { { description: 'changed' } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ description description_html @@ -159,7 +159,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do context 'when dates are changed' do let(:opts) { { start_date: Date.today } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ start_date updated_at @@ -180,7 +180,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do context 'when decription is changed' do let(:opts) { { description: 'description changed' } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ description description_html @@ -216,13 +216,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do context 'when state_event is close' do let(:opts) { { state_event: 'close' } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', - attributes: %w[ - closed_at - closed_by_id - state_id - updated_at - ] + include_examples 'does not publish WorkItems::WorkItemUpdatedEvent event' it 'closes the work item' do expect do @@ -239,12 +233,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do work_item.close! end - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', - attributes: %w[ - closed_at - state_id - updated_at - ] + include_examples 'does not publish WorkItems::WorkItemUpdatedEvent event' it 'reopens the work item' do expect do @@ -311,7 +300,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do end context 'for the description widget' do - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ description description_html @@ -379,7 +368,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do context 'for start and due date widget' do let(:updated_date) { 1.week.from_now.to_date } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ description description_html @@ -422,7 +411,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do let(:widget_params) { { hierarchy_widget: { children: [child_work_item] } } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ title title_html @@ -467,7 +456,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do context 'when work item validation fails' do let(:opts) { { title: '' } } - it_behaves_like 'do not publish WorkItems::WorkItemUpdatedEvent event' + include_examples 'does not publish WorkItems::WorkItemUpdatedEvent event' it 'returns validation errors' do expect(update_work_item[:message]).to contain_exactly("Title can't be blank") @@ -488,7 +477,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do let(:widget_params) { { milestone_widget: { milestone_id: milestone.id } } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ milestone_id updated_at @@ -529,7 +518,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do let_it_be(:user_todo) { create(:todo, target: work_item, user: developer, project: project, state: :pending) } let_it_be(:other_todo) { create(:todo, target: work_item, user: create(:user), project: project, state: :pending) } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ description description_html @@ -546,7 +535,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do context 'when action is mark_as_done' do let(:widget_params) { { current_user_todos_widget: { action: 'mark_as_done' } } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ updated_at updated_by_id @@ -589,7 +578,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do let(:label) { create(:label, project: project) } let(:opts) { { label_ids: [label1.id] } } - it_behaves_like 'publish WorkItems::WorkItemUpdatedEvent event', + include_examples 'publish WorkItems::WorkItemUpdatedEvent event', attributes: %w[ updated_at updated_by_id