Commit a648c45e authored by Eugenia Grieff's avatar Eugenia Grieff

Delegate author and project to the linked issue

- Remove validation in Requirement model
- Fix specs
parent 48f2b3cc
......@@ -18,11 +18,10 @@ module Mutations
current_user: context[:current_user],
params: args
).execute
error_message = requirement.errors.messages_for(:requirement_issue).to_sentence
{
requirement: requirement.valid? ? requirement : nil,
errors: error_message.present? ? Array.wrap(error_message) : []
errors: errors_on_object(requirement)
}
end
end
......
......@@ -34,21 +34,22 @@ module RequirementsManagement
has_internal_id :iid, scope: :project
validates :author, :project, presence: true
validate :only_requirement_type_issue
after_validation :invalidate_if_sync_error, on: [:update, :create]
delegate :title,
:description,
:project,
:project_id,
:author,
:author_id,
:description,
:description_html,
:title_html,
:cached_markdown_version,
to: :requirement_issue,
allow_nil: true
enum state: { opened: 1, archived: 2 }
scope :for_iid, -> (iid) { where(iid: iid) }
......@@ -119,29 +120,25 @@ module RequirementsManagement
errors.add(:requirement_issue, "must be a `requirement`. You cannot associate a Requirement with an issue of type #{requirement_issue.issue_type}.") if requirement_issue && !requirement_issue.requirement? && will_save_change_to_issue_id?
end
def requirement_issue_sync_error!(message: '')
self.requirement_issue_sync_error = true
return if message.blank?
self.errors.add(:requirement_issue, :invalid, message: message)
def requirement_issue_sync_error!(invalid_issue:)
self.invalid_requirement_issue = invalid_issue
end
def state
# Do not map state if requirement_issue not present or issue_type != requirement
return super unless requirement_issue&.requirement?
return unless requirement_issue&.requirement?
STATE_MAP[requirement_issue.state]
end
private
attr_accessor :requirement_issue_sync_error
attr_accessor :invalid_requirement_issue # Used to retrieve error messages
def invalidate_if_sync_error
return unless requirement_issue_sync_error
return unless invalid_requirement_issue
# Mirror errors from requirement issue so that users can adjust accordingly
errors = requirement_issue.errors.full_messages.to_sentence if requirement_issue
errors = invalid_requirement_issue.errors.full_messages.to_sentence if invalid_requirement_issue
errors = errors.presence || "Associated issue was invalid and changes could not be applied."
self.errors.add(:base, errors)
......
......@@ -17,17 +17,18 @@ module RequirementsManagement
synced_issue = perform_sync(requirement, attributes)
return synced_issue if synced_issue.valid?
log_sync_error(requirement, synced_issue)
end
end
requirement.requirement_issue_sync_error!(invalid_issue: synced_issue)
# Overriden on subclasses
# To sync on create or on update
def perform_sync(requirement, attributes)
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
::Gitlab::AppLogger.info(
message: "Requirement-Issue Sync: Associated issue could not be saved",
project_id: project.id,
user_id: current_user.id,
params: params
)
private
nil
end
end
def attrs_to_sync(requirement)
sync_params = RequirementsManagement::Requirement.sync_params
......@@ -35,19 +36,10 @@ module RequirementsManagement
requirement.attributes.with_indifferent_access.slice(*changed_attrs)
end
def log_sync_error(requirement, issue)
requirement.requirement_issue_sync_error!(
message: issue.errors.full_messages.to_sentence
)
::Gitlab::AppLogger.info(
message: "Requirement-Issue Sync: Associated issue could not be saved",
project_id: project.id,
user_id: current_user.id,
params: params
)
nil
# Overriden on subclasses
# To sync on create or on update
def perform_sync(requirement, attributes)
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
end
end
......@@ -17,12 +17,6 @@ module EE
super
end
end
override :create_note
def create_note(issue, current_commit)
state = issue.requirement? ? 'closed' : issue.state
::SystemNoteService.change_status(issue, issue.project, current_user, state, current_commit)
end
end
end
end
......@@ -28,10 +28,8 @@ module RequirementsManagement
requirement = project.requirements.new(attributes)
requirement.requirement_issue ||= sync_issue_for(requirement)
sync_errors = requirement.errors.messages_for(:requirement_issue).to_sentence
requirement.save
requirement.errors.add(:requirement_issue, sync_errors) if sync_errors
requirement
end
......
......@@ -7,13 +7,8 @@ FactoryBot.define do
title { generate(:title) }
title_html { "<h2>#{title}</h2>" }
requirement_issue do
association(:issue, issue_type: :requirement, project: project, author: author, title: title, description: description)
end
after(:create) do |requirement, evaluator|
if evaluator.state.to_sym == :archived
requirement.requirement_issue.update!(state: "closed")
end
issue_state = state.to_s == 'archived' ? 'closed' : 'opened'
association(:issue, issue_type: :requirement, project: project, author: author, title: title, description: description, state: issue_state)
end
end
end
......@@ -18,21 +18,34 @@ RSpec.describe RequirementsManagement::Requirement do
end
describe 'delegate' do
it { is_expected.to delegate_method(:title).to(:requirement_issue).allow_nil }
it { is_expected.to delegate_method(:description).to(:requirement_issue).allow_nil }
it { is_expected.to delegate_method(:project_id).to(:requirement_issue).allow_nil }
it { is_expected.to delegate_method(:author_id).to(:requirement_issue).allow_nil }
it { is_expected.to delegate_method(:description_html).to(:requirement_issue).allow_nil }
it { is_expected.to delegate_method(:title_html).to(:requirement_issue).allow_nil }
it { is_expected.to delegate_method(:cached_markdown_version).to(:requirement_issue).allow_nil }
subject { build(:requirement) }
delegated_attributes = %i[
project project_id author author_id title title_html
description description_html cached_markdown_version
]
delegated_attributes.each do |attr_name|
it { is_expected.to delegate_method(attr_name).to(:requirement_issue).allow_nil }
end
context 'with nil attributes' do
let_it_be(:requirement) { create(:requirement, project: project, author: user, description: 'Test', state: 'archived') }
(delegated_attributes - %i[project project_id title]).each do |attr_name|
it "returns delegated #{attr_name} value" do
requirement.update_attribute(attr_name, nil)
expect(requirement.send(attr_name)).not_to be_nil
expect(requirement.send(attr_name)).to eq(requirement.requirement_issue.send(attr_name))
end
end
end
end
describe 'validations' do
subject { build(:requirement) }
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:author) }
context 'with requirement issue' do
let(:ri) { create(:requirement_issue) }
......@@ -218,8 +231,8 @@ RSpec.describe RequirementsManagement::Requirement do
requirement.requirement_issue = nil
end
it 'returns the requirement stored state' do
expect(requirement.state).to eq('archived')
it 'returns nil' do
expect(requirement.state).to be_nil
end
end
......
......@@ -32,7 +32,7 @@ shared_examples 'sync requirement with issue state' do
subject
expect(issue.reload.state).to eq(issue_expected_state)
expect(requirement.reload.state).to eq(requirement_initial_state)
expect(requirement.reload.state).to be_nil
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