1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
class GroupDescendantsFinder
include Gitlab::Allowable
attr_reader :current_user, :parent_group, :params
def initialize(current_user: nil, parent_group:, params: {})
@current_user = current_user
@parent_group = parent_group
@params = params.reverse_merge(non_archived: true)
end
def execute
# 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)
end
def subgroup_count
@subgroup_count ||= subgroups.count
end
def project_count
@project_count ||= projects.count
end
private
def children
return @children if @children
projects_count = <<~PROJECTCOUNT
(SELECT COUNT(projects.id) AS preloaded_project_count
FROM projects WHERE projects.namespace_id = namespaces.id)
PROJECTCOUNT
subgroup_count = <<~SUBGROUPCOUNT
(SELECT COUNT(children.id) AS preloaded_subgroup_count
FROM namespaces children
WHERE children.parent_id = namespaces.id)
SUBGROUPCOUNT
member_count = <<~MEMBERCOUNT
(SELECT COUNT(members.user_id) 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.*',
projects_count,
subgroup_count,
member_count
]
subgroups_with_counts = subgroups.with_route.page(params[:page]).per(per_page).select(group_selects)
group_page_count = subgroups_with_counts.total_pages
subgroup_page = subgroups_with_counts.current_page
paginated_projects = projects.with_route.page(subgroup_page - group_page_count)
.per(per_page - subgroups_with_counts.size)
if params[:filter]
ancestors_for_project_search = ancestors_for_groups(Group.where(id: paginated_projects.select(:namespace_id)))
subgroups_with_counts = ancestors_for_project_search.with_route.select(group_selects) | subgroups_with_counts
end
@children = subgroups_with_counts + paginated_projects
end
def direct_child_groups
GroupsFinder.new(current_user,
parent: parent_group,
all_available: true).execute
end
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
Gitlab::GroupHierarchy.new(Group.where(id: parent_group))
.base_and_descendants
.where(visible_for_user)
end
def subgroups_matching_filter
all_visible_descendant_groups
.where.not(id: parent_group)
.search(params[:filter])
end
# 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'
def ancestors_for_groups(base_for_ancestors)
ancestors_for_parent = Gitlab::GroupHierarchy.new(Group.where(id: parent_group))
.base_and_ancestors
Gitlab::GroupHierarchy.new(base_for_ancestors)
.base_and_ancestors.where.not(id: ancestors_for_parent)
end
def subgroups
return Group.none unless Group.supports_nested_groups?
return Group.none unless can?(current_user, :read_group, parent_group)
# When filtering subgroups, we want to find all matches withing the tree of
# descendants to show to the user
groups = if params[:filter]
ancestors_for_groups(subgroups_matching_filter)
else
direct_child_groups
end
groups.order_by(sort)
end
def projects_for_user
Project.public_or_visible_to_user(current_user).non_archived
end
def direct_child_projects
projects_for_user.where(namespace: parent_group)
end
def projects_matching_filter
projects_for_user.search(params[:filter])
.where(namespace: all_visible_descendant_groups)
end
def projects
return Project.none unless can?(current_user, :read_group, parent_group)
projects = if params[:filter]
projects_matching_filter
else
direct_child_projects
end
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)
end
end
|