Commit 946d423b authored by Nick Thomas's avatar Nick Thomas

Merge branch '213334-add-model-resource-state-event-pd' into 'master'

Add ResourceStateEvent model

See merge request gitlab-org/gitlab!30982
parents 59530486 8d793951
# frozen_string_literal: true
module StateEventable
extend ActiveSupport::Concern
included do
has_many :resource_state_events
end
end
...@@ -17,6 +17,7 @@ class Issue < ApplicationRecord ...@@ -17,6 +17,7 @@ class Issue < ApplicationRecord
include IgnorableColumns include IgnorableColumns
include MilestoneEventable include MilestoneEventable
include WhereComposite include WhereComposite
include StateEventable
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......
...@@ -19,6 +19,7 @@ class MergeRequest < ApplicationRecord ...@@ -19,6 +19,7 @@ class MergeRequest < ApplicationRecord
include ShaAttribute include ShaAttribute
include IgnorableColumns include IgnorableColumns
include MilestoneEventable include MilestoneEventable
include StateEventable
sha_attribute :squash_commit_sha sha_attribute :squash_commit_sha
......
# frozen_string_literal: true
class ResourceStateEvent < ResourceEvent
include IssueResourceEvent
include MergeRequestResourceEvent
validate :exactly_one_issuable
# state is used for issue and merge request states.
enum state: Issue.available_states.merge(MergeRequest.available_states).merge(reopened: 5)
def self.issuable_attrs
%i(issue merge_request).freeze
end
end
...@@ -20,6 +20,7 @@ module Issuable ...@@ -20,6 +20,7 @@ module Issuable
copy_resource_label_events copy_resource_label_events
copy_resource_weight_events copy_resource_weight_events
copy_resource_milestone_events copy_resource_milestone_events
copy_resource_state_events
end end
private private
...@@ -47,8 +48,6 @@ module Issuable ...@@ -47,8 +48,6 @@ module Issuable
end end
def copy_resource_label_events def copy_resource_label_events
entity_key = new_entity.class.name.underscore.foreign_key
copy_events(ResourceLabelEvent.table_name, original_entity.resource_label_events) do |event| copy_events(ResourceLabelEvent.table_name, original_entity.resource_label_events) do |event|
event.attributes event.attributes
.except('id', 'reference', 'reference_html') .except('id', 'reference', 'reference_html')
...@@ -80,9 +79,18 @@ module Issuable ...@@ -80,9 +79,18 @@ module Issuable
end end
end end
def event_attributes_with_milestone(event, milestone) def copy_resource_state_events
entity_key = new_entity.class.name.underscore.foreign_key return unless state_events_supported?
copy_events(ResourceStateEvent.table_name, original_entity.resource_state_events) do |event|
event.attributes
.except('id')
.merge(entity_key => new_entity.id,
'state' => ResourceStateEvent.states[event.state])
end
end
def event_attributes_with_milestone(event, milestone)
event.attributes event.attributes
.except('id') .except('id')
.merge(entity_key => new_entity.id, .merge(entity_key => new_entity.id,
...@@ -102,12 +110,20 @@ module Issuable ...@@ -102,12 +110,20 @@ module Issuable
end end
def entity_key def entity_key
new_entity.class.name.parameterize('_').foreign_key new_entity.class.name.underscore.foreign_key
end end
def milestone_events_supported? def milestone_events_supported?
original_entity.respond_to?(:resource_milestone_events) && both_respond_to?(:resource_milestone_events)
new_entity.respond_to?(:resource_milestone_events) end
def state_events_supported?
both_respond_to?(:resource_state_events)
end
def both_respond_to?(method)
original_entity.respond_to?(method) &&
new_entity.respond_to?(method)
end end
end end
end end
......
...@@ -7,6 +7,18 @@ describe Issue do ...@@ -7,6 +7,18 @@ describe Issue do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
describe 'associations' do
subject { build(:issue) }
it { is_expected.to have_many(:resource_weight_events) }
end
describe 'modules' do
subject { build(:issue) }
it { is_expected.to include_module(EE::WeightEventable) }
end
context 'callbacks' do context 'callbacks' do
describe '.after_create' do describe '.after_create' do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
......
# frozen_string_literal: true
FactoryBot.define do
factory :resource_state_event do
issue { merge_request.nil? ? create(:issue) : nil }
merge_request { nil }
state { :opened }
user { issue&.author || merge_request&.author || create(:user) }
end
end
...@@ -11,6 +11,7 @@ issues: ...@@ -11,6 +11,7 @@ issues:
- resource_label_events - resource_label_events
- resource_weight_events - resource_weight_events
- resource_milestone_events - resource_milestone_events
- resource_state_events
- sent_notifications - sent_notifications
- sentry_issue - sentry_issue
- label_links - label_links
...@@ -119,6 +120,7 @@ merge_requests: ...@@ -119,6 +120,7 @@ merge_requests:
- notes - notes
- resource_label_events - resource_label_events
- resource_milestone_events - resource_milestone_events
- resource_state_events
- label_links - label_links
- labels - labels
- last_edited_by - last_edited_by
......
...@@ -19,6 +19,8 @@ describe Issue do ...@@ -19,6 +19,8 @@ describe Issue do
it { is_expected.to have_many(:design_versions) } it { is_expected.to have_many(:design_versions) }
it { is_expected.to have_one(:sentry_issue) } it { is_expected.to have_one(:sentry_issue) }
it { is_expected.to have_one(:alert_management_alert) } it { is_expected.to have_one(:alert_management_alert) }
it { is_expected.to have_many(:resource_milestone_events) }
it { is_expected.to have_many(:resource_state_events) }
describe 'versions.most_recent' do describe 'versions.most_recent' do
it 'returns the most recent version' do it 'returns the most recent version' do
...@@ -38,6 +40,8 @@ describe Issue do ...@@ -38,6 +40,8 @@ describe Issue do
it { is_expected.to include_module(Referable) } it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) } it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(Taskable) } it { is_expected.to include_module(Taskable) }
it { is_expected.to include_module(MilestoneEventable) }
it { is_expected.to include_module(StateEventable) }
it_behaves_like 'AtomicInternalId' do it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid } let(:internal_id_attribute) { :iid }
......
...@@ -20,6 +20,8 @@ describe MergeRequest do ...@@ -20,6 +20,8 @@ describe MergeRequest do
it { is_expected.to have_many(:user_mentions).class_name("MergeRequestUserMention") } it { is_expected.to have_many(:user_mentions).class_name("MergeRequestUserMention") }
it { is_expected.to belong_to(:milestone) } it { is_expected.to belong_to(:milestone) }
it { is_expected.to belong_to(:sprint) } it { is_expected.to belong_to(:sprint) }
it { is_expected.to have_many(:resource_milestone_events) }
it { is_expected.to have_many(:resource_state_events) }
context 'for forks' do context 'for forks' do
let!(:project) { create(:project) } let!(:project) { create(:project) }
...@@ -178,6 +180,8 @@ describe MergeRequest do ...@@ -178,6 +180,8 @@ describe MergeRequest do
it { is_expected.to include_module(Referable) } it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) } it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(Taskable) } it { is_expected.to include_module(Taskable) }
it { is_expected.to include_module(MilestoneEventable) }
it { is_expected.to include_module(StateEventable) }
it_behaves_like 'AtomicInternalId' do it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid } let(:internal_id_attribute) { :iid }
......
# frozen_string_literal: true
require 'spec_helper'
describe ResourceStateEvent, type: :model do
subject { build(:resource_state_event, issue: issue) }
let(:issue) { create(:issue) }
let(:merge_request) { create(:merge_request) }
it_behaves_like 'a resource event'
it_behaves_like 'a resource event for issues'
it_behaves_like 'a resource event for merge requests'
end
...@@ -114,5 +114,27 @@ describe Issuable::Clone::AttributesRewriter do ...@@ -114,5 +114,27 @@ describe Issuable::Clone::AttributesRewriter do
expect(event.state).to eq(expected_attrs[:state]) expect(event.state).to eq(expected_attrs[:state])
end end
end end
context 'with existing state events' do
let!(:event1) { create(:resource_state_event, issue: original_issue, state: 'opened') }
let!(:event2) { create(:resource_state_event, issue: original_issue, state: 'closed') }
let!(:event3) { create(:resource_state_event, issue: original_issue, state: 'reopened') }
it 'copies existing state events as expected' do
subject.execute
state_events = new_issue.reload.resource_state_events
expect(state_events.size).to eq(3)
expect_state_event(state_events.first, issue: new_issue, state: 'opened')
expect_state_event(state_events.second, issue: new_issue, state: 'closed')
expect_state_event(state_events.third, issue: new_issue, state: 'reopened')
end
def expect_state_event(event, expected_attrs)
expect(event.issue_id).to eq(expected_attrs[:issue]&.id)
expect(event.state).to eq(expected_attrs[:state])
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment