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.rb31
-rw-r--r--app/services/issues/close_service.rb1
-rw-r--r--app/services/issues/create_service.rb16
-rw-r--r--app/services/issues/duplicate_service.rb9
-rw-r--r--app/services/issues/move_service.rb14
-rw-r--r--app/services/issues/related_branches_service.rb2
-rw-r--r--app/services/issues/reopen_service.rb1
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/issues/zoom_link_service.rb1
9 files changed, 68 insertions, 11 deletions
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 9e72f6dad8d..0ed2b08b7b1 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -2,6 +2,8 @@
module Issues
class BaseService < ::IssuableBaseService
+ include IncidentManagement::UsageData
+
def hook_data(issue, action, old_associations: {})
hook_data = issue.to_hook_data(current_user, old_associations: old_associations)
hook_data[:object_attributes][:action] = action
@@ -17,6 +19,19 @@ module Issues
Issues::CloseService
end
+ NO_REBALANCING_NEEDED = ((RelativePositioning::MIN_POSITION * 0.9999)..(RelativePositioning::MAX_POSITION * 0.9999)).freeze
+
+ def rebalance_if_needed(issue)
+ return unless issue
+ return if issue.relative_position.nil?
+ return if NO_REBALANCING_NEEDED.cover?(issue.relative_position)
+
+ gates = [issue.project, issue.project.group].compact
+ return unless gates.any? { |gate| Feature.enabled?(:rebalance_issues, gate) }
+
+ IssueRebalancingWorker.perform_async(nil, issue.project_id)
+ end
+
private
def create_assignee_note(issue, old_assignees)
@@ -46,6 +61,22 @@ 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/close_service.rb b/app/services/issues/close_service.rb
index e431c766df8..c3677de015f 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -37,6 +37,7 @@ module Issues
execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
+ track_incident_action(current_user, issue, :incident_closed)
store_first_mentioned_in_commit_at(issue, closed_via) if closed_via.is_a?(MergeRequest)
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index c0194f5b847..fb7683f940d 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -5,31 +5,33 @@ module Issues
include SpamCheckMethods
include ResolveDiscussions
- def execute
+ def execute(skip_system_notes: false)
@issue = BuildService.new(project, current_user, params).execute
filter_spam_check_params
filter_resolve_discussion_params
- create(@issue)
+ create(@issue, skip_system_notes: skip_system_notes)
end
def before_create(issue)
spam_check(issue, current_user, action: :create)
- issue.move_to_end
# current_user (defined in BaseService) is not available within run_after_commit block
user = current_user
issue.run_after_commit do
NewIssueWorker.perform_async(issue.id, user.id)
+ IssuePlacementWorker.perform_async(nil, issue.project_id)
end
end
- def after_create(issuable)
- todo_service.new_issue(issuable, current_user)
+ def after_create(issue)
+ add_incident_label(issue)
+ todo_service.new_issue(issue, current_user)
user_agent_detail_service.create
- resolve_discussions_with_issue(issuable)
- delete_milestone_total_issue_counter_cache(issuable.milestone)
+ resolve_discussions_with_issue(issue)
+ delete_milestone_total_issue_counter_cache(issue.milestone)
+ track_incident_action(current_user, issue, :incident_created)
super
end
diff --git a/app/services/issues/duplicate_service.rb b/app/services/issues/duplicate_service.rb
index c936d75e277..feb496542c8 100644
--- a/app/services/issues/duplicate_service.rb
+++ b/app/services/issues/duplicate_service.rb
@@ -12,6 +12,8 @@ module Issues
close_service.new(project, current_user, {}).execute(duplicate_issue)
duplicate_issue.update(duplicated_to: canonical_issue)
+
+ relate_two_issues(duplicate_issue, canonical_issue)
end
private
@@ -23,7 +25,10 @@ module Issues
def create_issue_canonical_note(canonical_issue, duplicate_issue)
SystemNoteService.mark_canonical_issue_of_duplicate(canonical_issue, canonical_issue.project, current_user, duplicate_issue)
end
+
+ def relate_two_issues(duplicate_issue, canonical_issue)
+ params = { target_issuable: canonical_issue }
+ IssueLinks::CreateService.new(duplicate_issue, current_user, params).execute
+ end
end
end
-
-Issues::DuplicateService.prepend_if_ee('EE::Issues::DuplicateService')
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index ce1466307e1..60e0d1eec3d 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -38,6 +38,7 @@ module Issues
def update_old_entity
super
+ rewrite_related_issues
mark_as_moved
end
@@ -51,13 +52,24 @@ module Issues
}
new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
- CreateService.new(@target_project, @current_user, new_params).execute
+
+ # 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 mark_as_moved
original_entity.update(moved_to: new_entity)
end
+ def rewrite_related_issues
+ source_issue_links = IssueLink.for_source_issue(original_entity)
+ source_issue_links.update_all(source_id: new_entity.id)
+
+ target_issue_links = IssueLink.for_target_issue(original_entity)
+ target_issue_links.update_all(target_id: new_entity.id)
+ end
+
def notify_participants
notification_service.async.issue_moved(original_entity, new_entity, @current_user)
end
diff --git a/app/services/issues/related_branches_service.rb b/app/services/issues/related_branches_service.rb
index 46076218857..98d8412102f 100644
--- a/app/services/issues/related_branches_service.rb
+++ b/app/services/issues/related_branches_service.rb
@@ -24,7 +24,7 @@ module Issues
return unless target
- pipeline = project.pipeline_for(branch_name, target.sha)
+ pipeline = project.latest_pipeline(branch_name, target.sha)
pipeline.detailed_status(current_user) if can?(current_user, :read_pipeline, pipeline)
end
diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb
index 0ffe33dd317..e2b1b5400c7 100644
--- a/app/services/issues/reopen_service.rb
+++ b/app/services/issues/reopen_service.rb
@@ -13,6 +13,7 @@ module Issues
invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
delete_milestone_closed_issue_counter_cache(issue.milestone)
+ track_incident_action(current_user, issue, :incident_reopened)
end
issue
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index ac7baba3b7c..ce21b2e0275 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -22,6 +22,7 @@ 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
@@ -44,12 +45,14 @@ module Issues
create_assignee_note(issue, old_assignees)
notification_service.async.reassigned_issue(issue, current_user, old_assignees)
todo_service.reassigned_assignable(issue, current_user, old_assignees)
+ track_incident_action(current_user, issue, :incident_assigned)
end
if issue.previous_changes.include?('confidential')
# don't enqueue immediately to prevent todos removal in case of a mistake
TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, issue.id) if issue.confidential?
create_confidentiality_note(issue)
+ track_usage_event(:incident_management_incident_change_confidential, current_user.id)
end
added_labels = issue.labels - old_labels
@@ -83,6 +86,7 @@ module Issues
raise ActiveRecord::RecordNotFound unless issue_before || issue_after
issue.move_between(issue_before, issue_after)
+ rebalance_if_needed(issue)
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/issues/zoom_link_service.rb b/app/services/issues/zoom_link_service.rb
index 9572cf50564..1384e2f83b2 100644
--- a/app/services/issues/zoom_link_service.rb
+++ b/app/services/issues/zoom_link_service.rb
@@ -60,6 +60,7 @@ module Issues
if @issue.persisted?
# Save the meeting directly since we only want to update one meeting, not all
zoom_meeting.save
+ track_incident_action(current_user, issue, :incident_zoom_meeting)
success(message: _('Zoom meeting added'))
else
success(message: _('Zoom meeting added'), payload: { zoom_meetings: [zoom_meeting] })