2021-02-22 17:27:13 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Issues
|
|
|
|
class CloneService < Issuable::Clone::BaseService
|
|
|
|
CloneError = Class.new(StandardError)
|
|
|
|
|
|
|
|
def execute(issue, target_project, with_notes: false)
|
|
|
|
@target_project = target_project
|
|
|
|
@with_notes = with_notes
|
|
|
|
|
2021-11-18 22:05:49 +05:30
|
|
|
verify_can_clone_issue!(issue, target_project)
|
2021-02-22 17:27:13 +05:30
|
|
|
|
|
|
|
super(issue, target_project)
|
|
|
|
|
|
|
|
notify_participants
|
|
|
|
|
|
|
|
queue_copy_designs
|
|
|
|
|
|
|
|
new_entity
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
attr_reader :target_project
|
|
|
|
attr_reader :with_notes
|
|
|
|
|
2021-11-18 22:05:49 +05:30
|
|
|
def verify_can_clone_issue!(issue, target_project)
|
|
|
|
unless issue.supports_move_and_clone?
|
|
|
|
raise CloneError, s_('CloneIssue|Cannot clone issues of \'%{issue_type}\' type.') % { issue_type: issue.issue_type }
|
|
|
|
end
|
|
|
|
|
|
|
|
unless issue.can_clone?(current_user, target_project)
|
|
|
|
raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions!')
|
|
|
|
end
|
|
|
|
|
|
|
|
if target_project.pending_delete?
|
|
|
|
raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
def update_new_entity
|
|
|
|
# we don't call `super` because we want to be able to decide whether or not to copy all comments over.
|
|
|
|
update_new_entity_description
|
|
|
|
update_new_entity_attributes
|
|
|
|
copy_award_emoji
|
|
|
|
copy_notes if with_notes
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_old_entity
|
|
|
|
# no-op
|
|
|
|
# The base_service closes the old issue, we don't want that, so we override here so nothing happens.
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_new_entity
|
|
|
|
new_params = {
|
|
|
|
id: nil,
|
|
|
|
iid: nil,
|
2021-04-17 20:07:23 +05:30
|
|
|
relative_position: relative_position,
|
2021-02-22 17:27:13 +05:30
|
|
|
project: target_project,
|
|
|
|
author: current_user,
|
|
|
|
assignee_ids: original_entity.assignee_ids
|
|
|
|
}
|
|
|
|
|
|
|
|
new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
# spam checking is not necessary, as no new content is being created. Passing nil for
|
|
|
|
# spam_params will cause SpamActionService to skip checking and return a success response.
|
|
|
|
spam_params = nil
|
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
# Skip creation of system notes for existing attributes of the issue. The system notes of the old
|
|
|
|
# issue are copied over so we don't want to end up with duplicate notes.
|
2021-09-30 23:02:18 +05:30
|
|
|
CreateService.new(project: target_project, current_user: current_user, params: new_params, spam_params: spam_params).execute(skip_system_notes: true)
|
2021-02-22 17:27:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def queue_copy_designs
|
|
|
|
return unless original_entity.designs.present?
|
|
|
|
|
|
|
|
response = DesignManagement::CopyDesignCollection::QueueService.new(
|
|
|
|
current_user,
|
|
|
|
original_entity,
|
|
|
|
new_entity
|
|
|
|
).execute
|
|
|
|
|
|
|
|
log_error(response.message) if response.error?
|
|
|
|
end
|
|
|
|
|
|
|
|
def notify_participants
|
|
|
|
notification_service.async.issue_cloned(original_entity, new_entity, current_user)
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_note_from
|
|
|
|
SystemNoteService.noteable_cloned(new_entity, target_project,
|
|
|
|
original_entity, current_user,
|
|
|
|
direction: :from)
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_note_to
|
|
|
|
SystemNoteService.noteable_cloned(original_entity, old_project,
|
|
|
|
new_entity, current_user,
|
|
|
|
direction: :to)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-03-08 18:12:59 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
Issues::CloneService.prepend_mod_with('Issues::CloneService')
|