2018-12-05 23:21:45 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
# ProjectsFinder
|
|
|
|
#
|
|
|
|
# Used to filter Projects by set of params
|
|
|
|
#
|
|
|
|
# Arguments:
|
|
|
|
# current_user - which user use
|
|
|
|
# project_ids_relation: int[] - project ids to use
|
|
|
|
# params:
|
|
|
|
# trending: boolean
|
2017-09-10 17:25:29 +05:30
|
|
|
# owned: boolean
|
2017-08-17 22:00:37 +05:30
|
|
|
# non_public: boolean
|
|
|
|
# starred: boolean
|
|
|
|
# sort: string
|
|
|
|
# visibility_level: int
|
2021-09-04 01:27:46 +05:30
|
|
|
# tag: string[] - deprecated, use 'topic' instead
|
|
|
|
# topic: string[]
|
2022-08-13 15:12:31 +05:30
|
|
|
# topic_id: int
|
2017-08-17 22:00:37 +05:30
|
|
|
# personal: boolean
|
|
|
|
# search: string
|
2020-04-22 19:07:51 +05:30
|
|
|
# search_namespaces: boolean
|
2020-12-08 15:28:05 +05:30
|
|
|
# minimum_search_length: int
|
2017-08-17 22:00:37 +05:30
|
|
|
# non_archived: boolean
|
2018-11-18 11:00:15 +05:30
|
|
|
# archived: 'only' or boolean
|
|
|
|
# min_access_level: integer
|
2020-04-22 19:07:51 +05:30
|
|
|
# last_activity_after: datetime
|
|
|
|
# last_activity_before: datetime
|
2020-07-28 23:09:34 +05:30
|
|
|
# repository_storage: string
|
2022-04-04 11:22:00 +05:30
|
|
|
# not_aimed_for_deletion: boolean
|
2017-08-17 22:00:37 +05:30
|
|
|
#
|
2016-06-02 11:05:42 +05:30
|
|
|
class ProjectsFinder < UnionFinder
|
2018-03-17 18:26:18 +05:30
|
|
|
include CustomAttributesFilter
|
2023-06-20 00:43:36 +05:30
|
|
|
include UpdatedAtFilter
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
attr_accessor :params
|
|
|
|
attr_reader :current_user, :project_ids_relation
|
2014-09-02 18:07:02 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def initialize(params: {}, current_user: nil, project_ids_relation: nil)
|
|
|
|
@params = params
|
|
|
|
@current_user = current_user
|
|
|
|
@project_ids_relation = project_ids_relation
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
@params[:topic] ||= @params.delete(:tag) if @params[:tag].present?
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def execute
|
2017-09-10 17:25:29 +05:30
|
|
|
user = params.delete(:user)
|
|
|
|
collection =
|
|
|
|
if user
|
2018-12-05 23:21:45 +05:30
|
|
|
PersonalProjectsFinder.new(user, finder_params).execute(current_user) # rubocop: disable CodeReuse/Finder
|
2017-09-10 17:25:29 +05:30
|
|
|
else
|
|
|
|
init_collection
|
|
|
|
end
|
|
|
|
|
2020-03-13 15:44:24 +05:30
|
|
|
use_cte = params.delete(:use_cte)
|
|
|
|
collection = Project.wrap_with_cte(collection) if use_cte
|
2018-12-13 13:39:08 +05:30
|
|
|
collection = filter_projects(collection)
|
2021-01-03 14:25:43 +05:30
|
|
|
|
2023-04-23 21:23:45 +05:30
|
|
|
sort(collection)
|
2014-09-02 18:07:02 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def init_collection
|
2017-09-10 17:25:29 +05:30
|
|
|
if current_user
|
|
|
|
collection_with_user
|
|
|
|
else
|
|
|
|
collection_without_user
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
# EE would override this to add more filters
|
|
|
|
def filter_projects(collection)
|
2023-01-13 00:05:48 +05:30
|
|
|
collection = collection.without_deleted
|
2018-12-13 13:39:08 +05:30
|
|
|
collection = by_ids(collection)
|
|
|
|
collection = by_personal(collection)
|
|
|
|
collection = by_starred(collection)
|
|
|
|
collection = by_trending(collection)
|
2019-07-07 11:18:12 +05:30
|
|
|
collection = by_visibility_level(collection)
|
2021-09-04 01:27:46 +05:30
|
|
|
collection = by_topics(collection)
|
2022-08-13 15:12:31 +05:30
|
|
|
collection = by_topic_id(collection)
|
2018-12-13 13:39:08 +05:30
|
|
|
collection = by_search(collection)
|
|
|
|
collection = by_archived(collection)
|
|
|
|
collection = by_custom_attributes(collection)
|
2022-04-04 11:22:00 +05:30
|
|
|
collection = by_not_aimed_for_deletion(collection)
|
2020-04-22 19:07:51 +05:30
|
|
|
collection = by_last_activity_after(collection)
|
|
|
|
collection = by_last_activity_before(collection)
|
2023-03-04 22:38:38 +05:30
|
|
|
collection = by_language(collection)
|
2023-04-23 21:23:45 +05:30
|
|
|
collection = by_feature_availability(collection)
|
2023-06-20 00:43:36 +05:30
|
|
|
collection = by_updated_at(collection)
|
2021-04-29 21:17:54 +05:30
|
|
|
by_repository_storage(collection)
|
2018-12-13 13:39:08 +05:30
|
|
|
end
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
def collection_with_user
|
|
|
|
if owned_projects?
|
|
|
|
current_user.owned_projects
|
2018-11-18 11:00:15 +05:30
|
|
|
elsif min_access_level?
|
2019-07-07 11:18:12 +05:30
|
|
|
current_user.authorized_projects(params[:min_access_level])
|
2023-03-04 22:38:38 +05:30
|
|
|
elsif private_only? || impossible_visibility_level?
|
|
|
|
current_user.authorized_projects
|
2017-09-10 17:25:29 +05:30
|
|
|
else
|
2023-03-04 22:38:38 +05:30
|
|
|
Project.public_or_visible_to_user(current_user)
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
end
|
2015-11-26 14:37:03 +05:30
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
# Builds a collection for an anonymous user.
|
|
|
|
def collection_without_user
|
2018-11-18 11:00:15 +05:30
|
|
|
if private_only? || owned_projects? || min_access_level?
|
2017-09-10 17:25:29 +05:30
|
|
|
Project.none
|
2017-08-17 22:00:37 +05:30
|
|
|
else
|
2017-09-10 17:25:29 +05:30
|
|
|
Project.public_to_user
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
2020-01-01 13:55:28 +05:30
|
|
|
# This is an optimization - surprisingly PostgreSQL does not optimize
|
|
|
|
# for this.
|
|
|
|
#
|
2022-10-11 01:57:18 +05:30
|
|
|
# If the default visibility level and desired visibility level filter cancels
|
2020-01-01 13:55:28 +05:30
|
|
|
# each other out, don't use the SQL clause for visibility level in
|
2022-10-11 01:57:18 +05:30
|
|
|
# `Project.public_or_visible_to_user`. In fact, this then becomes equivalent
|
2020-01-01 13:55:28 +05:30
|
|
|
# to just authorized projects for the user.
|
|
|
|
#
|
|
|
|
# E.g.
|
|
|
|
# (EXISTS(<authorized_projects>) OR projects.visibility_level IN (10,20))
|
|
|
|
# AND "projects"."visibility_level" = 0
|
|
|
|
#
|
|
|
|
# is essentially
|
|
|
|
# EXISTS(<authorized_projects>) AND "projects"."visibility_level" = 0
|
|
|
|
#
|
|
|
|
# See https://gitlab.com/gitlab-org/gitlab/issues/37007
|
|
|
|
def impossible_visibility_level?
|
|
|
|
return unless params[:visibility_level].present?
|
|
|
|
|
|
|
|
public_visibility_levels = Gitlab::VisibilityLevel.levels_for_user(current_user)
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
!public_visibility_levels.include?(params[:visibility_level].to_i)
|
2020-01-01 13:55:28 +05:30
|
|
|
end
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
def owned_projects?
|
|
|
|
params[:owned].present?
|
|
|
|
end
|
2015-11-26 14:37:03 +05:30
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
def private_only?
|
|
|
|
params[:non_public].present?
|
2015-11-26 14:37:03 +05:30
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2018-11-18 11:00:15 +05:30
|
|
|
def min_access_level?
|
|
|
|
params[:min_access_level].present?
|
|
|
|
end
|
|
|
|
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-08-17 22:00:37 +05:30
|
|
|
def by_ids(items)
|
2019-12-26 22:10:19 +05:30
|
|
|
items = items.where(id: project_ids_relation) if project_ids_relation
|
2020-04-22 19:07:51 +05:30
|
|
|
items = items.where('projects.id > ?', params[:id_after]) if params[:id_after]
|
|
|
|
items = items.where('projects.id < ?', params[:id_before]) if params[:id_before]
|
2019-12-26 22:10:19 +05:30
|
|
|
items
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
def union(items)
|
|
|
|
find_union(items, Project).with_route
|
|
|
|
end
|
|
|
|
|
|
|
|
def by_personal(items)
|
2020-05-24 23:13:21 +05:30
|
|
|
params[:personal].present? && current_user ? items.personal(current_user) : items
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
def by_starred(items)
|
2020-05-24 23:13:21 +05:30
|
|
|
params[:starred].present? && current_user ? items.starred_by(current_user) : items
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def by_trending(items)
|
|
|
|
params[:trending].present? ? items.trending : items
|
|
|
|
end
|
|
|
|
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2019-07-07 11:18:12 +05:30
|
|
|
def by_visibility_level(items)
|
2017-08-17 22:00:37 +05:30
|
|
|
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
|
|
|
|
end
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
def by_topics(items)
|
2021-11-11 11:23:49 +05:30
|
|
|
return items unless params[:topic].present?
|
|
|
|
|
2021-11-18 22:05:49 +05:30
|
|
|
topics = params[:topic].instance_of?(String) ? params[:topic].split(',') : params[:topic]
|
|
|
|
topics.map(&:strip).uniq.reject(&:empty?).each do |topic|
|
2022-08-13 15:12:31 +05:30
|
|
|
items = items.with_topic_by_name(topic)
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
items
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2022-08-13 15:12:31 +05:30
|
|
|
def by_topic_id(items)
|
|
|
|
return items unless params[:topic_id].present?
|
|
|
|
|
|
|
|
topic = Projects::Topic.find_by(id: params[:topic_id]) # rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
return Project.none unless topic
|
|
|
|
|
|
|
|
items.with_topic(topic)
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def by_search(items)
|
|
|
|
params[:search] ||= params[:name]
|
2020-12-08 15:28:05 +05:30
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
return items if Feature.enabled?(:disable_anonymous_project_search, type: :ops) && current_user.nil?
|
2020-12-08 15:28:05 +05:30
|
|
|
return items.none if params[:search].present? && params[:minimum_search_length].present? && params[:search].length < params[:minimum_search_length].to_i
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
def by_not_aimed_for_deletion(items)
|
|
|
|
params[:not_aimed_for_deletion].present? ? items.not_aimed_for_deletion : items
|
|
|
|
end
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
def by_last_activity_after(items)
|
|
|
|
if params[:last_activity_after].present?
|
|
|
|
items.where("last_activity_at > ?", params[:last_activity_after]) # rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
else
|
|
|
|
items
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def by_last_activity_before(items)
|
|
|
|
if params[:last_activity_before].present?
|
|
|
|
items.where("last_activity_at < ?", params[:last_activity_before]) # rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
else
|
|
|
|
items
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
def by_repository_storage(items)
|
|
|
|
if params[:repository_storage].present?
|
|
|
|
items.where(repository_storage: params[:repository_storage]) # rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
else
|
|
|
|
items
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
def by_language(items)
|
2023-04-23 21:23:45 +05:30
|
|
|
if params[:language].present?
|
2023-03-04 22:38:38 +05:30
|
|
|
items.with_programming_language_id(params[:language])
|
|
|
|
else
|
|
|
|
items
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def sort(items)
|
2023-04-23 21:23:45 +05:30
|
|
|
return items.projects_order_id_desc unless params[:sort]
|
|
|
|
|
|
|
|
if params[:sort] == 'similarity' && params[:search].present?
|
|
|
|
return items.sorted_by_similarity_desc(params[:search], include_in_select: true)
|
2021-01-03 14:25:43 +05:30
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
|
|
|
|
items.sort_by_attribute(params[:sort])
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def by_archived(projects)
|
2018-03-17 18:26:18 +05:30
|
|
|
if params[:non_archived]
|
|
|
|
projects.non_archived
|
2018-11-18 11:00:15 +05:30
|
|
|
elsif params.key?(:archived)
|
2018-03-17 18:26:18 +05:30
|
|
|
if params[:archived] == 'only'
|
|
|
|
projects.archived
|
|
|
|
elsif Gitlab::Utils.to_boolean(params[:archived])
|
|
|
|
projects
|
|
|
|
else
|
|
|
|
projects.non_archived
|
|
|
|
end
|
|
|
|
else
|
|
|
|
projects
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2018-11-18 11:00:15 +05:30
|
|
|
|
2023-04-23 21:23:45 +05:30
|
|
|
def by_feature_availability(items)
|
|
|
|
items = items.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
|
|
|
|
items = items.with_merge_requests_available_for_user(current_user) if params[:with_merge_requests_enabled]
|
|
|
|
items
|
|
|
|
end
|
|
|
|
|
2018-11-18 11:00:15 +05:30
|
|
|
def finder_params
|
|
|
|
return {} unless min_access_level?
|
|
|
|
|
|
|
|
{ min_access_level: params[:min_access_level] }
|
|
|
|
end
|
2014-09-02 18:07:02 +05:30
|
|
|
end
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
ProjectsFinder.prepend_mod_with('ProjectsFinder')
|