Welcome to mirror list, hosted at ThFree Co, Russian Federation.

assigned_issues_count_service.rb « users « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 6590902587d825a1d506e6992786ec922332f23c (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
# frozen_string_literal: true

module Users
  class AssignedIssuesCountService < ::BaseCountService
    def initialize(current_user:, max_limit: User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT)
      @current_user = current_user
      @max_limit = max_limit
    end

    def cache_key
      ['users', @current_user.id, 'max_assigned_open_issues_count']
    end

    def cache_options
      { force: false, expires_in: User::COUNT_CACHE_VALIDITY_PERIOD }
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def uncached_count
      # When a user has many assigned issues, counting them all can be very slow.
      # As a workaround, we will short-circuit the counting query once the count reaches some threshold.
      #
      # Concretely, given a threshold, say 100 (= max_limit),
      # iterate through the first 100 issues, sorted by ID desc, assigned to the user using `issue_assignees` table.
      # For each issue iterated, use IssuesFinder to check if the issue should be counted.
      initializer = IssueAssignee
        .select(:issue_id).joins(", LATERAL (#{finder_constraint.to_sql}) as issues")
        .where(user_id: @current_user.id)
        .order(issue_id: :desc)
        .limit(1)
      recursive_finder = initializer.where("issue_assignees.issue_id < assigned_issues.issue_id")

      cte = <<~SQL
        WITH RECURSIVE assigned_issues AS (
          (
            #{initializer.to_sql}
          )
          UNION ALL
          (
            SELECT next_assigned_issue.issue_id
            FROM assigned_issues,
              LATERAL (
                #{recursive_finder.to_sql}
              ) next_assigned_issue
          )
        ) SELECT COUNT(*) FROM (SELECT * FROM assigned_issues LIMIT #{@max_limit}) issues
      SQL

      ApplicationRecord.connection.execute(cte).first["count"]
    end
    # rubocop: enable CodeReuse/ActiveRecord

    private

    # rubocop: disable CodeReuse/ActiveRecord
    def finder_constraint
      IssuesFinder.new(@current_user, assignee_id: @current_user.id, state: 'opened', non_archived: true)
                  .execute
                  .where("issues.id=issue_assignees.issue_id").limit(1)
    end
    # rubocop: enable CodeReuse/ActiveRecord
  end
end