Class: Issues::UpdateService

Inherits:
BaseService show all
Defined in:
app/services/issues/update_service.rb

Direct Known Subclasses

WorkItems::UpdateService

Constant Summary collapse

WORK_ITEM_DESCRIPTION_FIELDS =
%w[
  description description_html cached_markdown_version last_edited_by_id last_edited_at lock_version
].freeze

Constants inherited from BaseService

BaseService::EpicAssignmentError, BaseService::NO_REBALANCING_NEEDED

Constants included from Gitlab::Utils::UsageData

Gitlab::Utils::UsageData::DISTRIBUTED_HLL_FALLBACK, Gitlab::Utils::UsageData::FALLBACK, Gitlab::Utils::UsageData::HISTOGRAM_FALLBACK, Gitlab::Utils::UsageData::MAX_BUCKET_SIZE

Instance Attribute Summary

Attributes inherited from BaseContainerService

#container, #current_user, #group, #params, #project

Instance Method Summary collapse

Methods inherited from BaseService

#available_callbacks, #close_service, constructor_container_arg, #execute_hooks, #hook_data, #rebalance_if_needed, #reopen_service

Methods included from Gitlab::Utils::Override

#extended, extensions, #included, #method_added, #override, #prepended, #queue_verification, verify!

Methods included from IssueTypeHelpers

#create_issue_type_allowed?

Methods included from IncidentManagement::UsageData

#track_incident_action

Methods included from Gitlab::Utils::UsageData

#add, #add_metric, #alt_usage_data, #average, #count, #distinct_count, #estimate_batch_distinct_count, #histogram, #maximum_id, #measure_duration, #metrics_collection_metadata, #minimum_id, #redis_usage_data, #sum, #track_usage_event, #with_finished_at, #with_metadata, #with_prometheus_client

Methods inherited from IssuableBaseService

constructor_container_arg

Methods inherited from BaseContainerService

#group_container?, #namespace_container?, #project_container?, #project_group, #root_ancestor

Methods included from BaseServiceUtility

#deny_visibility_level, #event_service, #log_error, #log_info, #notification_service, #system_hook_service, #todo_service, #visibility_level

Methods included from Gitlab::Allowable

#can?, #can_all?, #can_any?

Constructor Details

#initialize(container:, current_user: nil, params: {}, perform_spam_check: false) ⇒ UpdateService

NOTE: For Issues::UpdateService, we default perform_spam_check to false, because spam_checking is not necessary in many cases, and we don’t want to require every caller to explicitly pass it to disable spam checking.



12
13
14
15
# File 'app/services/issues/update_service.rb', line 12

def initialize(container:, current_user: nil, params: {}, perform_spam_check: false)
  super(container: container, current_user: current_user, params: params)
  @perform_spam_check = perform_spam_check
end

Instance Method Details

#before_update(issue, skip_spam_check: false) ⇒ Object



30
31
32
33
34
35
36
# File 'app/services/issues/update_service.rb', line 30

def before_update(issue, skip_spam_check: false)
  change_work_item_type(issue)

  return if skip_spam_check || !perform_spam_check

  issue.check_for_spam(user: current_user, action: :update)
end

#change_issue_duplicate(issue) ⇒ Object



93
94
95
96
97
98
99
100
101
102
# File 'app/services/issues/update_service.rb', line 93

def change_issue_duplicate(issue)
  canonical_issue_id = params.delete(:canonical_issue_id)
  return unless canonical_issue_id

  canonical_issue = Issue.find_by_id(canonical_issue_id)

  if canonical_issue
    Issues::DuplicateService.new(container: project, current_user: current_user).execute(issue, canonical_issue)
  end
end

#change_work_item_type(issue) ⇒ Object



38
39
40
41
42
43
44
45
46
# File 'app/services/issues/update_service.rb', line 38

def change_work_item_type(issue)
  return unless params[:issue_type].present?

  type_id = find_work_item_type_id(params[:issue_type])

  # For custom types we need to use the abstraction layer so pass the work item type object
  # See https://gitlab.com/groups/gitlab-org/-/work_items/20291
  issue.work_item_type_id = type_id
end

#execute(issue) ⇒ Object



17
18
19
20
21
22
# File 'app/services/issues/update_service.rb', line 17

def execute(issue)
  handle_move_between_ids(issue)

  change_issue_duplicate(issue)
  move_issue_to_new_container(issue) || clone_issue(issue) || update_task_event(issue) || update(issue)
end

#handle_assignee_changes(issue, old_assignees) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
# File 'app/services/issues/update_service.rb', line 75

def handle_assignee_changes(issue, old_assignees)
  return if issue.assignees == old_assignees

  create_assignee_note(issue, old_assignees)
  Gitlab::ResourceEvents::AssignmentEventRecorder.new(parent: issue, old_assignees: old_assignees).record
  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)
  execute_flow_triggers(issue, issue.assignees - old_assignees, :assign)

  GraphqlTriggers.issuable_assignees_updated(issue)
end

#handle_changes(issue, options) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'app/services/issues/update_service.rb', line 48

def handle_changes(issue, options)
  super
  old_associations = options.fetch(:old_associations, {})
  old_labels = old_associations.fetch(:labels, [])
  old_mentioned_users = old_associations.fetch(:mentioned_users, [])
  old_assignees = old_associations.fetch(:assignees, [])
  old_severity = old_associations[:severity]

  if has_changes?(issue, old_labels: old_labels, old_assignees: old_assignees)
    todo_service.resolve_todos_for_target(issue, current_user)
  end

  if issue.previous_changes.include?('title') ||
      issue.previous_changes.include?('description')
    todo_service.update_issue(issue, current_user, old_mentioned_users)
  end

  handle_assignee_changes(issue, old_assignees)
  handle_confidential_change(issue)
  handle_added_labels(issue, old_labels)
  handle_added_mentions(issue, old_mentioned_users)
  handle_severity_change(issue, old_severity)
  handle_escalation_status_change(issue)
  handle_issue_type_change(issue)
  handle_date_changes(issue)
end

#handle_task_changes(issuable) ⇒ Object



88
89
90
91
# File 'app/services/issues/update_service.rb', line 88

def handle_task_changes(issuable)
  todo_service.resolve_todos_for_target(issuable, current_user)
  todo_service.update_issue(issuable, current_user)
end

#move_issue_to_new_container(issue) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'app/services/issues/update_service.rb', line 104

def move_issue_to_new_container(issue)
  target_container = params.delete(:target_container)

  return unless target_container &&
    issue.can_move?(current_user, target_container) &&
    !move_to_same_container?(issue, target_container)

  update(issue)

  move_service_container = target_container.is_a?(Project) ? target_container.project_namespace : target_container

  ::WorkItems::DataSync::MoveService.new(
    work_item: issue, current_user: current_user, target_namespace: move_service_container
  ).execute[:work_item]
end

#update(issue) ⇒ Object



24
25
26
27
28
# File 'app/services/issues/update_service.rb', line 24

def update(issue)
  create_merge_request_from_quick_action

  super
end