debian-mirror-gitlab/app/services/users/assigned_issues_count_service.rb
2023-03-04 22:38:38 +05:30

63 lines
2.2 KiB
Ruby

# 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