diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 9a74e36870be06392d74bf34c208325f5256b45a..001c83ccb4b4d2636dbcf6a4cb3c41ca7318fb89 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -49,6 +49,32 @@ class IssuableFinder execute.find_by(*params) end + # We often get counts for each state by running a query per state, and + # counting those results. This is typically slower than running one query + # (even if that query is slower than any of the individual state queries) and + # grouping and counting within that query. + # + def count_by_state + count_params = params.merge(state: nil, sort: nil) + labels_count = label_names.any? ? label_names.count : 1 + finder = self.class.new(current_user, count_params) + counts = Hash.new(0) + + # Searching by label includes a GROUP BY in the query, but ours will be last + # because it is added last. Searching by multiple labels also includes a row + # per issuable, so we have to count those in Ruby - which is bad, but still + # better than performing multiple queries. + # + finder.execute.reorder(nil).group(:state).count.each do |key, value| + counts[Array(key).last.to_sym] += value / labels_count + end + + counts[:all] = counts.values.sum + counts[:opened] += counts[:reopened] + + counts + end + def group return @group if defined?(@group) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 6584aa3edd55d6620856b92d0c8806bd7367f3bc..8231f8fa334b871d419eac3c7ef78e6ec6bc0f6d 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -180,12 +180,9 @@ module IssuablesHelper end def issuables_count_for_state(issuable_type, state) - issuables_finder = public_send("#{issuable_type}_finder") - - params = issuables_finder.params.merge(state: state) - finder = issuables_finder.class.new(issuables_finder.current_user, params) - - finder.execute.page(1).total_count + @counts ||= {} + @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state + @counts[issuable_type][state] end IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page] @@ -195,6 +192,7 @@ module IssuablesHelper opts = params.with_indifferent_access opts[:state] = state opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) + opts.delete_if { |_, value| value.blank? } hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-')) end diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index baa6d5f8206ed93c95b53123749adbc844f82e42..26b349e8a624af90dfa7ef4f835b4951dd170a88 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,4 +1,4 @@ -- if @issues.reorder(nil).any? +- if @issues.to_a.any? - @issues.group_by(&:project).each do |group| .panel.panel-default.panel-small - project = group[0] diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index ca3178395c1508f3a139ae04c9e8a50cc936aac5..2f3605b4d27547fdf103cdc11df6158d92b30d43 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -1,4 +1,4 @@ -- if @merge_requests.reorder(nil).any? +- if @merge_requests.to_a.any? - @merge_requests.group_by(&:target_project).each do |group| .panel.panel-default.panel-small - project = group[0] diff --git a/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml new file mode 100644 index 0000000000000000000000000000000000000000..01b19a47ecdff2d6ca6b6243b8e614c57e4ae3d3 --- /dev/null +++ b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml @@ -0,0 +1,4 @@ +--- +title: Speed up issuable dashboards +merge_request: +author: diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb index 02605d6b70e0bac8e0a6d1aa8b4e658b930d0fb3..92cf3de544821ff2318fa41e18056e125ac7617b 100644 --- a/spec/support/matchers/have_issuable_counts.rb +++ b/spec/support/matchers/have_issuable_counts.rb @@ -1,9 +1,9 @@ RSpec::Matchers.define :have_issuable_counts do |opts| - match do |actual| - expected_counts = opts.map do |state, count| - "#{state.to_s.humanize} #{count}" - end + expected_counts = opts.map do |state, count| + "#{state.to_s.humanize} #{count}" + end + match do |actual| actual.within '.issues-state-filters' do expected_counts.each do |expected_count| expect(actual).to have_content(expected_count)