Commit 8c4d5909 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'dashboard-projects-controller-query-performance' into 'master'

Improve various parts of Dashboard::ProjectsController

See merge request !13319
parents 5bf65c93 bd08fff6
...@@ -45,8 +45,10 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -45,8 +45,10 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end end
def load_projects(finder_params) def load_projects(finder_params)
ProjectsFinder.new(params: finder_params, current_user: current_user) ProjectsFinder
.execute.includes(:route, namespace: :route) .new(params: finder_params, current_user: current_user)
.execute
.includes(:route, :creator, namespace: :route)
end end
def load_events def load_events
......
...@@ -225,6 +225,26 @@ module ProjectsHelper ...@@ -225,6 +225,26 @@ module ProjectsHelper
end end
end end
# Returns true if any projects are present.
#
# If the relation has a LIMIT applied we'll cast the relation to an Array
# since repeated any? checks would otherwise result in multiple COUNT queries
# being executed.
#
# If no limit is applied we'll just issue a COUNT since the result set could
# be too large to load into memory.
def any_projects?(projects)
if projects.limit_value
projects.to_a.any?
else
projects.except(:offset).any?
end
end
def has_projects_or_name?(projects, params)
!!(params[:name] || any_projects?(projects))
end
private private
def repo_children_classes(field) def repo_children_classes(field)
......
...@@ -632,7 +632,11 @@ class User < ActiveRecord::Base ...@@ -632,7 +632,11 @@ class User < ActiveRecord::Base
end end
def projects_limit_left def projects_limit_left
projects_limit - personal_projects.count projects_limit - personal_projects_count
end
def personal_projects_count
@personal_projects_count ||= personal_projects.count
end end
def projects_limit_percent def projects_limit_percent
...@@ -646,16 +650,14 @@ class User < ActiveRecord::Base ...@@ -646,16 +650,14 @@ class User < ActiveRecord::Base
events = events.where(project_id: project_ids) if project_ids events = events.where(project_id: project_ids) if project_ids
# Use the latest event that has not been pushed or merged recently # Use the latest event that has not been pushed or merged recently
events.recent.find do |event| events.includes(:project).recent.find do |event|
project = Project.find_by_id(event.project_id) next unless event.project.repository.branch_exists?(event.branch_name)
next unless project
merge_requests = MergeRequest.where("created_at >= ?", event.created_at)
if project.repository.branch_exists?(event.branch_name) .where(source_project_id: event.project.id,
merge_requests = MergeRequest.where("created_at >= ?", event.created_at) source_branch: event.branch_name)
.where(source_project_id: project.id,
source_branch: event.branch_name) merge_requests.empty?
merge_requests.empty?
end
end end
end end
......
...@@ -13,10 +13,8 @@ ...@@ -13,10 +13,8 @@
- if show_callout?('user_callout_dismissed') - if show_callout?('user_callout_dismissed')
= render 'shared/user_callout' = render 'shared/user_callout'
- if @projects.any? || params[:name] - if has_projects_or_name?(@projects, params)
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
- if @projects.any? || params[:name]
= render 'projects' = render 'projects'
- else - else
= render "zero_authorized_projects" = render "zero_authorized_projects"
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%div{ class: container_class } %div{ class: container_class }
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
- if @projects.any? || params[:filter_projects] - if params[:filter_projects] || any_projects?(@projects)
= render 'projects' = render 'projects'
- else - else
%h3 You don't have starred projects yet %h3 You don't have starred projects yet
......
- if @projects.any? - if any_projects?(@projects)
.project-item-select-holder .project-item-select-holder
= project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled] = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled]
%a.btn.btn-new.new-project-item-select-button %a.btn.btn-new.new-project-item-select-button
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
- load_pipeline_status(projects) - load_pipeline_status(projects)
.js-projects-list-holder .js-projects-list-holder
- if projects.any? - if any_projects?(projects)
%ul.projects-list %ul.projects-list
- projects.each_with_index do |project, i| - projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
......
---
title: "Improve performance of checking for projects on the projects dashboard"
merge_request:
author:
---
title: Eager load project creators for project dashboards
merge_request:
author:
---
title: Memoize the number of personal projects a user has to reduce COUNT queries
merge_request:
author:
---
title: Remove redundant query when retrieving the most recent push of a user
merge_request:
author:
...@@ -411,4 +411,48 @@ describe ProjectsHelper do ...@@ -411,4 +411,48 @@ describe ProjectsHelper do
end end
end end
end end
describe '#has_projects_or_name?' do
let(:projects) do
create(:project)
Project.all
end
it 'returns true when there are projects' do
expect(helper.has_projects_or_name?(projects, {})).to eq(true)
end
it 'returns true when there are no projects but a name is given' do
expect(helper.has_projects_or_name?(Project.none, name: 'foo')).to eq(true)
end
it 'returns false when there are no projects and there is no name' do
expect(helper.has_projects_or_name?(Project.none, {})).to eq(false)
end
end
describe '#any_projects?' do
before do
create(:project)
end
it 'returns true when projects will be returned' do
expect(helper.any_projects?(Project.all)).to eq(true)
end
it 'returns false when no projects will be returned' do
expect(helper.any_projects?(Project.none)).to eq(false)
end
it 'only executes a single query when a LIMIT is applied' do
relation = Project.limit(1)
recorder = ActiveRecord::QueryRecorder.new do
2.times do
helper.any_projects?(relation)
end
end
expect(recorder.count).to eq(1)
end
end
end end
...@@ -1976,4 +1976,28 @@ describe User do ...@@ -1976,4 +1976,28 @@ describe User do
expect(user.allow_password_authentication?).to be_falsey expect(user.allow_password_authentication?).to be_falsey
end end
end end
describe '#personal_projects_count' do
it 'returns the number of personal projects using a single query' do
user = build(:user)
projects = double(:projects, count: 1)
expect(user).to receive(:personal_projects).once.and_return(projects)
2.times do
expect(user.personal_projects_count).to eq(1)
end
end
end
describe '#projects_limit_left' do
it 'returns the number of projects that can be created by the user' do
user = build(:user)
allow(user).to receive(:projects_limit).and_return(10)
allow(user).to receive(:personal_projects_count).and_return(5)
expect(user.projects_limit_left).to eq(5)
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment