summaryrefslogtreecommitdiff
path: root/app/workers/issue_placement_worker.rb
blob: cfd72b90a420ff2646374207fd2c04d08cee7821 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# frozen_string_literal: true

# todo: remove this worker and it's queue definition from all_queues after Issues::PlacementWorker is deployed
# We want to keep it for one release in case some jobs are already scheduled in the old queue so we need the worker
# to be available to finish those. All new jobs will be queued into the new queue.
class IssuePlacementWorker
  include ApplicationWorker

  data_consistency :always

  sidekiq_options retry: 3

  idempotent!
  deduplicate :until_executed, including_scheduled: true
  feature_category :team_planning
  urgency :high
  worker_resource_boundary :cpu
  weight 2

  # Move at most the most recent 100 issues
  QUERY_LIMIT = 100

  # rubocop: disable CodeReuse/ActiveRecord
  def perform(issue_id, project_id = nil)
    issue = find_issue(issue_id, project_id)
    return unless issue

    # Temporary disable moving null elements because of performance problems
    # For more information check https://gitlab.com/gitlab-com/gl-infra/production/-/issues/4321
    return if issue.blocked_for_repositioning?

    # Move the oldest 100 unpositioned items to the end.
    # This is to deal with out-of-order execution of the worker,
    # while preserving creation order.
    to_place = Issue
      .relative_positioning_query_base(issue)
      .with_null_relative_position
      .order({ created_at: :asc }, { id: :asc })
      .limit(QUERY_LIMIT + 1)
      .to_a

    leftover = to_place.pop if to_place.count > QUERY_LIMIT

    Issue.move_nulls_to_end(to_place)
    Issues::BaseService.new(project: nil).rebalance_if_needed(to_place.max_by(&:relative_position))
    IssuePlacementWorker.perform_async(nil, leftover.project_id) if leftover.present?
  rescue RelativePositioning::NoSpaceLeft => e
    Gitlab::ErrorTracking.log_exception(e, issue_id: issue_id, project_id: project_id)
    IssueRebalancingWorker.perform_async(nil, *root_namespace_id_to_rebalance(issue, project_id))
  end

  def find_issue(issue_id, project_id)
    return Issue.id_in(issue_id).take if issue_id

    project = Project.id_in(project_id).take
    return unless project

    project.issues.take
  end
  # rubocop: enable CodeReuse/ActiveRecord

  private

  def root_namespace_id_to_rebalance(issue, project_id)
    project_id = project_id.presence || issue.project_id
    Project.find(project_id)&.self_or_root_group_ids
  end
end