2018-09-11 15:08:34 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-03-03 05:35:04 -05:00
|
|
|
# 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-05-26 10:31:37 -04:00
|
|
|
# owned: boolean
|
2017-03-03 05:35:04 -05:00
|
|
|
# non_public: boolean
|
|
|
|
# starred: boolean
|
|
|
|
# sort: string
|
|
|
|
# visibility_level: int
|
|
|
|
# tags: string[]
|
|
|
|
# personal: boolean
|
|
|
|
# search: string
|
|
|
|
# non_archived: boolean
|
2018-07-11 14:17:18 -04:00
|
|
|
# archived: 'only' or boolean
|
2018-07-08 15:43:06 -04:00
|
|
|
# min_access_level: integer
|
2017-03-03 05:35:04 -05:00
|
|
|
#
|
2016-03-20 16:03:53 -04:00
|
|
|
class ProjectsFinder < UnionFinder
|
2017-09-18 09:03:24 -04:00
|
|
|
include CustomAttributesFilter
|
|
|
|
|
2019-09-13 09:26:31 -04:00
|
|
|
prepend_if_ee('::EE::ProjectsFinder') # rubocop: disable Cop/InjectEnterpriseEditionModule
|
|
|
|
|
2017-03-03 05:35:04 -05:00
|
|
|
attr_accessor :params
|
|
|
|
attr_reader :current_user, :project_ids_relation
|
2014-02-25 12:15:08 -05:00
|
|
|
|
2017-03-03 05:35:04 -05:00
|
|
|
def initialize(params: {}, current_user: nil, project_ids_relation: nil)
|
|
|
|
@params = params
|
|
|
|
@current_user = current_user
|
|
|
|
@project_ids_relation = project_ids_relation
|
|
|
|
end
|
|
|
|
|
|
|
|
def execute
|
2017-06-29 13:20:59 -04:00
|
|
|
user = params.delete(:user)
|
|
|
|
collection =
|
|
|
|
if user
|
2018-08-27 11:31:01 -04:00
|
|
|
PersonalProjectsFinder.new(user, finder_params).execute(current_user) # rubocop: disable CodeReuse/Finder
|
2017-06-29 13:20:59 -04:00
|
|
|
else
|
|
|
|
init_collection
|
|
|
|
end
|
|
|
|
|
2018-10-12 10:10:34 -04:00
|
|
|
collection = filter_projects(collection)
|
2017-06-13 10:44:55 -04:00
|
|
|
sort(collection)
|
2014-02-25 12:15:08 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2017-03-03 05:35:04 -05:00
|
|
|
def init_collection
|
2017-06-13 10:44:55 -04:00
|
|
|
if current_user
|
|
|
|
collection_with_user
|
|
|
|
else
|
|
|
|
collection_without_user
|
|
|
|
end
|
|
|
|
end
|
2016-03-10 16:08:11 -05:00
|
|
|
|
2018-10-12 10:10:34 -04:00
|
|
|
# EE would override this to add more filters
|
|
|
|
def filter_projects(collection)
|
|
|
|
collection = by_ids(collection)
|
|
|
|
collection = by_personal(collection)
|
|
|
|
collection = by_starred(collection)
|
|
|
|
collection = by_trending(collection)
|
2019-05-07 07:08:25 -04:00
|
|
|
collection = by_visibility_level(collection)
|
2018-10-12 10:10:34 -04:00
|
|
|
collection = by_tags(collection)
|
|
|
|
collection = by_search(collection)
|
|
|
|
collection = by_archived(collection)
|
|
|
|
collection = by_custom_attributes(collection)
|
|
|
|
collection = by_deleted_status(collection)
|
|
|
|
collection
|
|
|
|
end
|
|
|
|
|
2017-06-13 10:44:55 -04:00
|
|
|
def collection_with_user
|
|
|
|
if owned_projects?
|
|
|
|
current_user.owned_projects
|
2018-07-08 15:43:06 -04:00
|
|
|
elsif min_access_level?
|
2019-05-07 07:08:25 -04:00
|
|
|
current_user.authorized_projects(params[:min_access_level])
|
2017-05-26 10:31:37 -04:00
|
|
|
else
|
2017-06-13 10:44:55 -04:00
|
|
|
if private_only?
|
|
|
|
current_user.authorized_projects
|
|
|
|
else
|
2019-04-09 23:36:09 -04:00
|
|
|
Project.public_or_visible_to_user(current_user)
|
2017-06-13 10:44:55 -04:00
|
|
|
end
|
2017-05-26 10:31:37 -04:00
|
|
|
end
|
2017-06-13 10:44:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Builds a collection for an anonymous user.
|
|
|
|
def collection_without_user
|
2018-07-08 15:43:06 -04:00
|
|
|
if private_only? || owned_projects? || min_access_level?
|
2017-06-13 10:44:55 -04:00
|
|
|
Project.none
|
|
|
|
else
|
|
|
|
Project.public_to_user
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def owned_projects?
|
|
|
|
params[:owned].present?
|
|
|
|
end
|
2015-11-18 06:21:06 -05:00
|
|
|
|
2017-06-13 10:44:55 -04:00
|
|
|
def private_only?
|
|
|
|
params[:non_public].present?
|
2015-11-18 06:21:06 -05:00
|
|
|
end
|
2017-03-03 05:35:04 -05:00
|
|
|
|
2018-07-08 15:43:06 -04:00
|
|
|
def min_access_level?
|
|
|
|
params[:min_access_level].present?
|
|
|
|
end
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-03-03 05:35:04 -05:00
|
|
|
def by_ids(items)
|
UNION of SELECT/WHERE is faster than WHERE on UNION
Instead of applying WHERE on a UNION, apply the WHERE on each of the seperate
SELECT statements, and do UNION on that.
Local tests with about 2_000_000 projects:
- 1_500_000 private projects
- 40_000 internal projects
- 400_000 public projects
For the API endpoint `/api/v4/projects?visibility=private` the slowest query was:
```sql
SELECT "projects".*
FROM "projects"
WHERE ...
```
The original query took 1073.8ms.
The query refactored to UNION of SELECT/WHERE took 2.3ms.
The original query was:
```sql
SELECT "projects".*
FROM "projects"
WHERE "projects"."pending_delete" = $1
AND (projects.id IN
(SELECT "projects"."id"
FROM "projects"
INNER JOIN "project_authorizations" ON "projects"."id" = "project_authorizations"."project_id"
WHERE "projects"."pending_delete" = 'f'
AND "project_authorizations"."user_id" = 23
UNION SELECT "projects"."id"
FROM "projects"
WHERE "projects"."visibility_level" IN (20,
10)))
AND "projects"."visibility_level" = $2
AND "projects"."archived" = $3
ORDER BY "projects"."created_at" DESC
LIMIT 20
OFFSET 0 [["pending_delete", "f"],
["visibility_level", 0],
["archived", "f"]]
```
The refactored query:
```sql
SELECT "projects".*
FROM "projects"
WHERE "projects"."pending_delete" = $1
AND (projects.id IN
(SELECT "projects"."id"
FROM "projects"
INNER JOIN "project_authorizations" ON "projects"."id" = "project_authorizations"."project_id"
WHERE "projects"."pending_delete" = 'f'
AND "project_authorizations"."user_id" = 23
AND "projects"."visibility_level" = 0
AND "projects"."archived" = 'f'
UNION SELECT "projects"."id"
FROM "projects"
WHERE "projects"."visibility_level" IN (20,
10)
AND "projects"."visibility_level" = 0
AND "projects"."archived" = 'f'))
ORDER BY "projects"."created_at" DESC
LIMIT 20
OFFSET 0 [["pending_delete", "f"]]
```
2017-05-24 09:03:45 -04:00
|
|
|
project_ids_relation ? items.where(id: project_ids_relation) : items
|
2017-03-03 05:35:04 -05:00
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-03-03 05:35:04 -05:00
|
|
|
|
|
|
|
def union(items)
|
|
|
|
find_union(items, Project).with_route
|
|
|
|
end
|
|
|
|
|
|
|
|
def by_personal(items)
|
|
|
|
(params[:personal].present? && current_user) ? items.personal(current_user) : items
|
|
|
|
end
|
|
|
|
|
2017-05-23 16:38:12 -04:00
|
|
|
def by_starred(items)
|
|
|
|
(params[:starred].present? && current_user) ? items.starred_by(current_user) : items
|
|
|
|
end
|
|
|
|
|
2017-05-26 09:39:38 -04:00
|
|
|
def by_trending(items)
|
|
|
|
params[:trending].present? ? items.trending : items
|
|
|
|
end
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2019-05-07 07:08:25 -04:00
|
|
|
def by_visibility_level(items)
|
2017-03-03 05:35:04 -05:00
|
|
|
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
|
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-03-03 05:35:04 -05:00
|
|
|
|
|
|
|
def by_tags(items)
|
|
|
|
params[:tag].present? ? items.tagged_with(params[:tag]) : items
|
|
|
|
end
|
|
|
|
|
|
|
|
def by_search(items)
|
|
|
|
params[:search] ||= params[:name]
|
|
|
|
params[:search].present? ? items.search(params[:search]) : items
|
|
|
|
end
|
|
|
|
|
2018-09-12 16:52:30 -04:00
|
|
|
def by_deleted_status(items)
|
|
|
|
params[:without_deleted].present? ? items.without_deleted : items
|
|
|
|
end
|
|
|
|
|
2017-03-03 05:35:04 -05:00
|
|
|
def sort(items)
|
2018-04-04 05:19:47 -04:00
|
|
|
params[:sort].present? ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
|
2017-03-03 05:35:04 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def by_archived(projects)
|
2017-08-11 05:09:17 -04:00
|
|
|
if params[:non_archived]
|
|
|
|
projects.non_archived
|
2018-07-11 14:17:18 -04:00
|
|
|
elsif params.key?(:archived)
|
2017-08-11 05:09:17 -04:00
|
|
|
if params[:archived] == 'only'
|
|
|
|
projects.archived
|
|
|
|
elsif Gitlab::Utils.to_boolean(params[:archived])
|
|
|
|
projects
|
|
|
|
else
|
|
|
|
projects.non_archived
|
|
|
|
end
|
|
|
|
else
|
|
|
|
projects
|
|
|
|
end
|
2017-03-03 05:35:04 -05:00
|
|
|
end
|
2018-07-08 15:43:06 -04:00
|
|
|
|
|
|
|
def finder_params
|
|
|
|
return {} unless min_access_level?
|
|
|
|
|
|
|
|
{ min_access_level: params[:min_access_level] }
|
|
|
|
end
|
2014-02-25 12:15:08 -05:00
|
|
|
end
|