Refactor GroupProjectsFinder#init_collection

This optimises how GroupProjectsFinder builds it collection, producing
simpler and faster queries in the process. It also cleans up the code a
bit to make it easier to understand.
This commit is contained in:
Yorick Peterse 2017-06-15 15:19:25 +02:00
parent 73bf9413b9
commit c9e277ee01
No known key found for this signature in database
GPG key ID: EDD30D2BEB691AC9
5 changed files with 106 additions and 38 deletions

View file

@ -33,7 +33,8 @@ class EventsFinder
private private
def by_current_user_access(events) def by_current_user_access(events)
events.merge(ProjectsFinder.new(current_user: current_user).execute).references(:project) events.merge(ProjectsFinder.new(current_user: current_user).execute).
joins(:project)
end end
def by_action(events) def by_action(events)

View file

@ -29,35 +29,69 @@ class GroupProjectsFinder < ProjectsFinder
private private
def init_collection def init_collection
only_owned = options.fetch(:only_owned, false) projects = if current_user
only_shared = options.fetch(:only_shared, false) collection_with_user
else
collection_without_user
end
projects = [] union(projects)
end
if current_user def collection_with_user
if group.users.include?(current_user) if group.users.include?(current_user)
projects << group.projects unless only_shared if only_shared?
projects << group.shared_projects unless only_owned [shared_projects]
elsif only_owned?
[owned_projects]
else else
unless only_shared [shared_projects, owned_projects]
projects << group.projects.visible_to_user(current_user)
projects << group.projects.public_to_user(current_user)
end
unless only_owned
projects << group.shared_projects.visible_to_user(current_user)
projects << group.shared_projects.public_to_user(current_user)
end
end end
else else
projects << group.projects.public_only unless only_shared if only_shared?
projects << group.shared_projects.public_only unless only_owned [shared_projects.public_or_visible_to_user(current_user)]
elsif only_owned?
[owned_projects.public_or_visible_to_user(current_user)]
else
[
owned_projects.public_or_visible_to_user(current_user),
shared_projects.public_or_visible_to_user(current_user)
]
end
end end
end
projects def collection_without_user
if only_shared?
[shared_projects.public_only]
elsif only_owned?
[owned_projects.public_only]
else
[shared_projects.public_only, owned_projects.public_only]
end
end end
def union(items) def union(items)
find_union(items, Project) if items.one?
items.first
else
find_union(items, Project)
end
end
def only_owned?
options.fetch(:only_owned, false)
end
def only_shared?
options.fetch(:only_shared, false)
end
def owned_projects
group.projects
end
def shared_projects
group.shared_projects
end end
end end

View file

@ -58,27 +58,11 @@ class ProjectsFinder < UnionFinder
if private_only? if private_only?
current_user.authorized_projects current_user.authorized_projects
else else
collection_with_user_and_public_projects Project.public_or_visible_to_user(current_user)
end end
end end
end end
# Builds a collection for a signed in user that includes additional projects
# such as public and internal ones.
#
# This method manually constructs some WHERE conditions in order to ensure the
# produced query is as efficient as possible.
def collection_with_user_and_public_projects
levels = Gitlab::VisibilityLevel.levels_for_user(current_user)
authorized = current_user.project_authorizations.
select(1).
where('project_id = projects.id')
Project.where('EXISTS (?) OR projects.visibility_level IN (?)',
authorized,
levels)
end
# Builds a collection for an anonymous user. # Builds a collection for an anonymous user.
def collection_without_user def collection_without_user
if private_only? || owned_projects? if private_only? || owned_projects?

View file

@ -266,6 +266,23 @@ class Project < ActiveRecord::Base
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
# Returns a collection of projects that is either public or visible to the
# logged in user.
def self.public_or_visible_to_user(user = nil)
if user
authorized = user.
project_authorizations.
select(1).
where('project_authorizations.project_id = projects.id')
levels = Gitlab::VisibilityLevel.levels_for_user(user)
where('EXISTS (?) OR projects.visibility_level IN (?)', authorized, levels)
else
public_to_user
end
end
# project features may be "disabled", "internal" or "enabled". If "internal", # project features may be "disabled", "internal" or "enabled". If "internal",
# they are only available to team members. This scope returns projects where # they are only available to team members. This scope returns projects where
# the feature is either enabled, or internal with permission for the user. # the feature is either enabled, or internal with permission for the user.

View file

@ -2060,4 +2060,36 @@ describe Project, models: true do
expect(project.last_repository_updated_at.to_i).to eq(project.created_at.to_i) expect(project.last_repository_updated_at.to_i).to eq(project.created_at.to_i)
end end
end end
describe '.public_or_visible_to_user' do
let!(:user) { create(:user) }
let!(:private_project) do
create(:empty_project, :private, creator: user, namespace: user.namespace)
end
let!(:public_project) { create(:empty_project, :public) }
context 'with a user' do
let(:projects) do
Project.all.public_or_visible_to_user(user)
end
it 'includes projects the user has access to' do
expect(projects).to include(private_project)
end
it 'includes projects the user can see' do
expect(projects).to include(public_project)
end
end
context 'without a user' do
it 'only includes public projects' do
projects = Project.all.public_or_visible_to_user
expect(projects).to eq([public_project])
end
end
end
end end