64 lines
2.2 KiB
Ruby
64 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
|