Commit c3f47d6d authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Move scopes to ProjectFeature

This avoids doing an index scan on the projects table
parent 5e9b0c12
...@@ -576,18 +576,12 @@ class Project < ApplicationRecord ...@@ -576,18 +576,12 @@ class Project < ApplicationRecord
.where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%") .where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%")
end end
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) { scope :with_feature_enabled, ->(feature) {
access_level_attribute = ProjectFeature.arel_table[ProjectFeature.access_level_attribute(feature)] with_project_feature.merge(ProjectFeature.with_feature_enabled(feature))
enabled_feature = access_level_attribute.gt(ProjectFeature::DISABLED).or(access_level_attribute.eq(nil))
with_project_feature.where(enabled_feature)
} }
# Picks a feature where the level is exactly that given.
scope :with_feature_access_level, ->(feature, level) { scope :with_feature_access_level, ->(feature, level) {
access_level_attribute = ProjectFeature.access_level_attribute(feature) with_project_feature.merge(ProjectFeature.with_feature_access_level(feature, level))
with_project_feature.where(project_features: { access_level_attribute => level })
} }
# Picks projects which use the given programming language # Picks projects which use the given programming language
...@@ -688,37 +682,8 @@ class Project < ApplicationRecord ...@@ -688,37 +682,8 @@ class Project < ApplicationRecord
end end
end end
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns projects where
# the feature is either public, enabled, or internal with permission for the user.
# Note: this scope doesn't enforce that the user has access to the projects, it just checks
# that the user has access to the feature. It's important to use this scope with others
# that checks project authorizations first (e.g. `filter_by_feature_visibility`).
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user) def self.with_feature_available_for_user(feature, user)
visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC] with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user))
if user&.can_read_all_resources?
with_feature_enabled(feature)
elsif user
min_access_level = ProjectFeature.required_minimum_access_level(feature)
column = ProjectFeature.quoted_access_level_column(feature)
with_project_feature
.where("#{column} IS NULL OR #{column} IN (:public_visible) OR (#{column} = :private_visible AND EXISTS (:authorizations))",
{
public_visible: visible,
private_visible: ProjectFeature::PRIVATE,
authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
})
else
# This has to be added to include features whose value is nil in the db
visible << nil
with_feature_access_level(feature, visible)
end
end end
def self.projects_user_can(projects, user, action) def self.projects_user_can(projects, user, action)
......
...@@ -83,6 +83,52 @@ class ProjectFeature < ApplicationRecord ...@@ -83,6 +83,52 @@ class ProjectFeature < ApplicationRecord
end end
end end
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
feature_access_level_attribute = arel_table[access_level_attribute(feature)]
enabled_feature = feature_access_level_attribute.gt(DISABLED).or(feature_access_level_attribute.eq(nil))
where(enabled_feature)
}
# Picks a feature where the level is exactly that given.
scope :with_feature_access_level, ->(feature, level) {
feature_access_level_attribute = access_level_attribute(feature)
where(project_features: { feature_access_level_attribute => level })
}
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns features where
# the feature is either public, enabled, or internal with permission for the user.
# Note: this scope doesn't enforce that the user has access to the projects, it just checks
# that the user has access to the feature. It's important to use this scope with others
# that checks project authorizations first (e.g. `filter_by_feature_visibility`).
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
visible = [ENABLED, PUBLIC]
if user&.can_read_all_resources?
with_feature_enabled(feature)
elsif user
min_access_level = required_minimum_access_level(feature)
column = quoted_access_level_column(feature)
where("#{column} IS NULL OR #{column} IN (:public_visible) OR (#{column} = :private_visible AND EXISTS (:authorizations))",
{
public_visible: visible,
private_visible: PRIVATE,
authorizations: user.authorizations_for_projects(min_access_level: min_access_level, related_project_column: 'project_features.project_id')
})
else
# This has to be added to include features whose value is nil in the db
visible << nil
with_feature_access_level(feature, visible)
end
end
def public_pages? def public_pages?
return true unless Gitlab.config.pages.access_control return true unless Gitlab.config.pages.access_control
......
...@@ -84,10 +84,11 @@ module Gitlab ...@@ -84,10 +84,11 @@ module Gitlab
# use IN(project_ids...) instead. It's the intersection of two users so # use IN(project_ids...) instead. It's the intersection of two users so
# the list will be (relatively) short # the list will be (relatively) short
@contributed_project_ids ||= projects.distinct.pluck(:id) @contributed_project_ids ||= projects.distinct.pluck(:id)
authed_projects = Project.where(id: @contributed_project_ids) authed_projects = ProjectFeature
.with_feature_available_for_user(feature, current_user) .with_feature_available_for_user(feature, current_user)
.where(project_id: @contributed_project_ids)
.reorder(nil) .reorder(nil)
.select(:id) .select(:project_id)
conditions = t[:created_at].gteq(date_from.beginning_of_day) conditions = t[:created_at].gteq(date_from.beginning_of_day)
.and(t[:created_at].lteq(@contributor_time_instance.today.end_of_day)) .and(t[:created_at].lteq(@contributor_time_instance.today.end_of_day))
......
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