2017-09-19 11:11:09 +00:00
|
|
|
class GroupDescendantsFinder
|
2017-09-04 14:23:55 +00:00
|
|
|
include Gitlab::Allowable
|
|
|
|
|
|
|
|
attr_reader :current_user, :parent_group, :params
|
|
|
|
|
2017-10-02 08:12:57 +00:00
|
|
|
PROJECT_COUNT_SQL = <<~PROJECTCOUNT.freeze
|
|
|
|
(SELECT COUNT(*) AS preloaded_project_count
|
|
|
|
FROM projects
|
|
|
|
WHERE projects.namespace_id = namespaces.id
|
|
|
|
AND projects.archived IS NOT true)
|
|
|
|
PROJECTCOUNT
|
|
|
|
SUBGROUP_COUNT_SQL = <<~SUBGROUPCOUNT.freeze
|
|
|
|
(SELECT COUNT(*) AS preloaded_subgroup_count
|
|
|
|
FROM namespaces children
|
|
|
|
WHERE children.parent_id = namespaces.id)
|
|
|
|
SUBGROUPCOUNT
|
|
|
|
MEMBER_COUNT_SQL = <<~MEMBERCOUNT.freeze
|
|
|
|
(SELECT COUNT(*) AS preloaded_member_count
|
|
|
|
FROM members
|
|
|
|
WHERE members.source_type = 'Namespace'
|
|
|
|
AND members.source_id = namespaces.id
|
|
|
|
AND members.requested_at IS NULL)
|
|
|
|
MEMBERCOUNT
|
|
|
|
|
|
|
|
GROUP_SELECTS = ['namespaces.*',
|
|
|
|
PROJECT_COUNT_SQL,
|
|
|
|
SUBGROUP_COUNT_SQL,
|
|
|
|
MEMBER_COUNT_SQL].freeze
|
|
|
|
|
2017-09-19 09:15:57 +00:00
|
|
|
def initialize(current_user: nil, parent_group:, params: {})
|
2017-09-04 14:23:55 +00:00
|
|
|
@current_user = current_user
|
|
|
|
@parent_group = parent_group
|
2017-09-21 07:20:37 +00:00
|
|
|
@params = params.reverse_merge(non_archived: true)
|
2017-09-04 14:23:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def execute
|
2017-09-26 19:31:32 +00:00
|
|
|
# The children array might be extended with the ancestors of projects when
|
|
|
|
# filtering. In that case, take the maximum so the aray does not get limited
|
|
|
|
# Otherwise, allow paginating through the search results
|
|
|
|
#
|
|
|
|
total_count = [children.size, subgroup_count + project_count].max
|
|
|
|
Kaminari.paginate_array(children, total_count: total_count)
|
2017-09-04 14:23:55 +00:00
|
|
|
end
|
|
|
|
|
2017-09-04 18:01:58 +00:00
|
|
|
def subgroup_count
|
2017-09-26 19:31:32 +00:00
|
|
|
@subgroup_count ||= subgroups.count
|
2017-09-04 18:01:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def project_count
|
2017-09-26 19:31:32 +00:00
|
|
|
@project_count ||= projects.count
|
2017-09-04 14:23:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def children
|
2017-09-26 09:22:52 +00:00
|
|
|
return @children if @children
|
|
|
|
|
2017-10-02 08:12:57 +00:00
|
|
|
subgroups_with_counts = subgroups.with_route
|
|
|
|
.page(params[:page]).per(per_page)
|
|
|
|
.select(GROUP_SELECTS)
|
2017-09-26 19:31:32 +00:00
|
|
|
|
2017-10-02 10:54:12 +00:00
|
|
|
paginated_projects = paginate_projects_after_groups(subgroups_with_counts)
|
2017-09-26 09:22:52 +00:00
|
|
|
|
|
|
|
if params[:filter]
|
2017-09-26 19:31:32 +00:00
|
|
|
ancestors_for_project_search = ancestors_for_groups(Group.where(id: paginated_projects.select(:namespace_id)))
|
2017-10-02 08:12:57 +00:00
|
|
|
subgroups_with_counts = ancestors_for_project_search.with_route.select(GROUP_SELECTS) | subgroups_with_counts
|
2017-09-26 09:22:52 +00:00
|
|
|
end
|
|
|
|
|
2017-09-26 19:31:32 +00:00
|
|
|
@children = subgroups_with_counts + paginated_projects
|
2017-09-04 14:23:55 +00:00
|
|
|
end
|
|
|
|
|
2017-10-02 10:54:12 +00:00
|
|
|
def paginate_projects_after_groups(loaded_subgroups)
|
|
|
|
# We adjust the pagination for projects for the combination with groups:
|
|
|
|
# - We limit the first page (page 0) where we show projects:
|
|
|
|
# Page size = 20: 17 groups, 3 projects
|
|
|
|
# - We ofset the page to start at 0 after the group pages:
|
|
|
|
# 3 pages of projects:
|
|
|
|
# - currently on page 3: Show page 0 (first page) limited to the number of
|
|
|
|
# projects that still fit the page (no offset)
|
|
|
|
# - currently on page 4: Show page 1 show all projects, offset by the number
|
|
|
|
# of projects shown on project-page 0.
|
|
|
|
group_page_count = loaded_subgroups.total_pages
|
|
|
|
subgroup_page = loaded_subgroups.current_page
|
|
|
|
group_last_page_count = subgroups.page(group_page_count).count
|
|
|
|
project_page = subgroup_page - group_page_count
|
|
|
|
offset = if project_page.zero? || group_page_count.zero?
|
|
|
|
0
|
|
|
|
else
|
|
|
|
per_page - group_last_page_count
|
|
|
|
end
|
|
|
|
|
|
|
|
projects.with_route.page(project_page)
|
|
|
|
.per(per_page - loaded_subgroups.size)
|
|
|
|
.padding(offset)
|
|
|
|
end
|
|
|
|
|
2017-09-19 11:11:09 +00:00
|
|
|
def direct_child_groups
|
2017-09-06 13:28:07 +00:00
|
|
|
GroupsFinder.new(current_user,
|
|
|
|
parent: parent_group,
|
|
|
|
all_available: true).execute
|
|
|
|
end
|
|
|
|
|
2017-09-26 18:06:08 +00:00
|
|
|
def all_visible_descendant_groups
|
|
|
|
groups_table = Group.arel_table
|
|
|
|
visible_for_user = if current_user
|
|
|
|
groups_table[:id].in(
|
|
|
|
Arel::Nodes::SqlLiteral.new(GroupsFinder.new(current_user, all_available: true).execute.select(:id).to_sql)
|
|
|
|
)
|
|
|
|
else
|
|
|
|
groups_table[:visibility_level].eq(Gitlab::VisibilityLevel::PUBLIC)
|
|
|
|
end
|
|
|
|
|
2017-09-26 09:22:52 +00:00
|
|
|
Gitlab::GroupHierarchy.new(Group.where(id: parent_group))
|
|
|
|
.base_and_descendants
|
2017-09-26 18:06:08 +00:00
|
|
|
.where(visible_for_user)
|
2017-09-06 13:28:07 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def subgroups_matching_filter
|
2017-09-26 18:06:08 +00:00
|
|
|
all_visible_descendant_groups
|
2017-09-26 09:22:52 +00:00
|
|
|
.where.not(id: parent_group)
|
|
|
|
.search(params[:filter])
|
|
|
|
end
|
|
|
|
|
2017-10-02 05:55:44 +00:00
|
|
|
# When filtering we want all to preload all the ancestors upto the specified
|
|
|
|
# parent group.
|
|
|
|
#
|
|
|
|
# - root
|
|
|
|
# - subgroup
|
|
|
|
# - nested-group
|
|
|
|
# - project
|
|
|
|
#
|
|
|
|
# So when searching 'project', on the 'subgroup' page we want to preload
|
|
|
|
# 'nested-group' but not 'subgroup' or 'root'
|
2017-09-26 09:22:52 +00:00
|
|
|
def ancestors_for_groups(base_for_ancestors)
|
2017-10-02 05:55:44 +00:00
|
|
|
ancestors_for_parent = Gitlab::GroupHierarchy.new(Group.where(id: parent_group))
|
|
|
|
.base_and_ancestors
|
2017-09-26 09:22:52 +00:00
|
|
|
Gitlab::GroupHierarchy.new(base_for_ancestors)
|
2017-10-02 05:55:44 +00:00
|
|
|
.base_and_ancestors.where.not(id: ancestors_for_parent)
|
2017-09-06 13:28:07 +00:00
|
|
|
end
|
|
|
|
|
2017-09-04 18:01:58 +00:00
|
|
|
def subgroups
|
2017-09-04 14:23:55 +00:00
|
|
|
return Group.none unless Group.supports_nested_groups?
|
|
|
|
return Group.none unless can?(current_user, :read_group, parent_group)
|
|
|
|
|
2017-09-19 11:11:09 +00:00
|
|
|
# When filtering subgroups, we want to find all matches withing the tree of
|
|
|
|
# descendants to show to the user
|
2017-09-06 13:28:07 +00:00
|
|
|
groups = if params[:filter]
|
2017-09-26 09:22:52 +00:00
|
|
|
ancestors_for_groups(subgroups_matching_filter)
|
2017-09-06 13:28:07 +00:00
|
|
|
else
|
2017-09-19 11:11:09 +00:00
|
|
|
direct_child_groups
|
2017-09-06 13:28:07 +00:00
|
|
|
end
|
2017-09-26 19:31:32 +00:00
|
|
|
groups.order_by(sort)
|
2017-09-04 14:23:55 +00:00
|
|
|
end
|
|
|
|
|
2017-09-26 09:22:52 +00:00
|
|
|
def projects_for_user
|
|
|
|
Project.public_or_visible_to_user(current_user).non_archived
|
|
|
|
end
|
|
|
|
|
2017-09-19 09:46:07 +00:00
|
|
|
def direct_child_projects
|
2017-09-26 09:22:52 +00:00
|
|
|
projects_for_user.where(namespace: parent_group)
|
2017-09-06 13:28:07 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def projects_matching_filter
|
2017-09-26 09:22:52 +00:00
|
|
|
projects_for_user.search(params[:filter])
|
2017-09-26 19:31:32 +00:00
|
|
|
.where(namespace: all_visible_descendant_groups)
|
2017-09-06 13:28:07 +00:00
|
|
|
end
|
|
|
|
|
2017-09-04 14:23:55 +00:00
|
|
|
def projects
|
|
|
|
return Project.none unless can?(current_user, :read_group, parent_group)
|
|
|
|
|
2017-09-06 13:28:07 +00:00
|
|
|
projects = if params[:filter]
|
|
|
|
projects_matching_filter
|
|
|
|
else
|
2017-09-19 09:46:07 +00:00
|
|
|
direct_child_projects
|
2017-09-06 13:28:07 +00:00
|
|
|
end
|
2017-09-26 19:31:32 +00:00
|
|
|
projects.order_by(sort)
|
|
|
|
end
|
|
|
|
|
|
|
|
def sort
|
|
|
|
params.fetch(:sort, 'id_asc')
|
|
|
|
end
|
|
|
|
|
|
|
|
def per_page
|
|
|
|
params.fetch(:per_page, Kaminari.config.default_per_page)
|
2017-09-04 14:23:55 +00:00
|
|
|
end
|
|
|
|
end
|