summaryrefslogtreecommitdiff
path: root/app/services/issues
diff options
context:
space:
mode:
Diffstat (limited to 'app/services/issues')
-rw-r--r--app/services/issues/base_service.rb16
-rw-r--r--app/services/issues/clone_service.rb90
-rw-r--r--app/services/issues/create_service.rb16
-rw-r--r--app/services/issues/export_csv_service.rb2
-rw-r--r--app/services/issues/update_service.rb15
5 files changed, 120 insertions, 19 deletions
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 978ea6fe9bc..25f319da03b 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -73,22 +73,6 @@ module Issues
Milestones::IssuesCountService.new(milestone).delete_cache
end
-
- # Applies label "incident" (creates it if missing) to incident issues.
- # Please use in "after" hooks only to ensure we are not appyling
- # labels prematurely.
- def add_incident_label(issue)
- return unless issue.incident?
-
- label = ::IncidentManagement::CreateIncidentLabelService
- .new(project, current_user)
- .execute
- .payload[:label]
-
- return if issue.label_ids.include?(label.id)
-
- issue.labels << label
- end
end
end
diff --git a/app/services/issues/clone_service.rb b/app/services/issues/clone_service.rb
new file mode 100644
index 00000000000..789da312958
--- /dev/null
+++ b/app/services/issues/clone_service.rb
@@ -0,0 +1,90 @@
+# 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
+
+ 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
+
+ super(issue, target_project)
+
+ notify_participants
+
+ queue_copy_designs
+
+ new_entity
+ end
+
+ private
+
+ attr_reader :target_project
+ attr_reader :with_notes
+
+ 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,
+ project: target_project,
+ author: current_user,
+ assignee_ids: original_entity.assignee_ids
+ }
+
+ new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
+
+ # 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.
+ CreateService.new(target_project, current_user, new_params).execute(skip_system_notes: true)
+ 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
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index fb7683f940d..44de8eb6389 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -49,6 +49,22 @@ module Issues
def user_agent_detail_service
UserAgentDetailService.new(@issue, @request)
end
+
+ # Applies label "incident" (creates it if missing) to incident issues.
+ # For use in "after" hooks only to ensure we are not appyling
+ # labels prematurely.
+ def add_incident_label(issue)
+ return unless issue.incident?
+
+ label = ::IncidentManagement::CreateIncidentLabelService
+ .new(project, current_user)
+ .execute
+ .payload[:label]
+
+ return if issue.label_ids.include?(label.id)
+
+ issue.labels << label
+ end
end
end
diff --git a/app/services/issues/export_csv_service.rb b/app/services/issues/export_csv_service.rb
index 1dcdfb9faea..8f513632929 100644
--- a/app/services/issues/export_csv_service.rb
+++ b/app/services/issues/export_csv_service.rb
@@ -34,7 +34,7 @@ module Issues
private
def associations_to_preload
- %i(author assignees timelogs)
+ %i(author assignees timelogs milestone)
end
def header_to_value_hash
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index b9832400302..127ed04cf51 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -9,7 +9,7 @@ module Issues
handle_move_between_ids(issue)
filter_spam_check_params
change_issue_duplicate(issue)
- move_issue_to_new_project(issue) || update_task_event(issue) || update(issue)
+ move_issue_to_new_project(issue) || clone_issue(issue) || update_task_event(issue) || update(issue)
end
def update(issue)
@@ -34,7 +34,6 @@ module Issues
end
def after_update(issue)
- add_incident_label(issue)
IssuesChannel.broadcast_to(issue, event: 'updated') if Gitlab::ActionCable::Config.in_app? || Feature.enabled?(:broadcast_issue_updates, issue.project)
end
@@ -127,6 +126,18 @@ module Issues
private
+ def clone_issue(issue)
+ target_project = params.delete(:target_clone_project)
+ with_notes = params.delete(:clone_with_notes)
+
+ return unless target_project &&
+ issue.can_clone?(current_user, target_project)
+
+ # we've pre-empted this from running in #execute, so let's go ahead and update the Issue now.
+ update(issue)
+ Issues::CloneService.new(project, current_user).execute(issue, target_project, with_notes: with_notes)
+ end
+
def create_merge_request_from_quick_action
create_merge_request_params = params.delete(:create_merge_request)
return unless create_merge_request_params