Skip to content

Commit 32e41b5

Browse files
author
Douwe Maan
committed
Merge branch 'bvl-parent-preloading' into 'master'
Fix filtering projects & groups on group pages Closes #40785 See merge request gitlab-org/gitlab-ce!16584
2 parents f344f7c + 26c4e47 commit 32e41b5

File tree

6 files changed

+102
-14
lines changed

6 files changed

+102
-14
lines changed

app/controllers/concerns/group_tree.rb

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ module GroupTree
22
# rubocop:disable Gitlab/ModuleWithInstanceVariables
33
def render_group_tree(groups)
44
@groups = if params[:filter].present?
5-
Gitlab::GroupHierarchy.new(groups.search(params[:filter]))
5+
# We find the ancestors by ID of the search results here.
6+
# Otherwise the ancestors would also have filters applied,
7+
# which would cause them not to be preloaded.
8+
group_ids = groups.search(params[:filter]).select(:id)
9+
Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
610
.base_and_ancestors
711
else
812
# Only show root groups if no parent-id is given

app/finders/group_descendants_finder.rb

+28-13
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@ def initialize(current_user: nil, parent_group:, params: {})
2727
end
2828

2929
def execute
30-
# The children array might be extended with the ancestors of projects when
31-
# filtering. In that case, take the maximum so the array does not get limited
32-
# Otherwise, allow paginating through all results
30+
# The children array might be extended with the ancestors of projects and
31+
# subgroups when filtering. In that case, take the maximum so the array does
32+
# not get limited otherwise, allow paginating through all results.
3333
#
3434
all_required_elements = children
35-
all_required_elements |= ancestors_for_projects if params[:filter]
35+
if params[:filter]
36+
all_required_elements |= ancestors_of_filtered_subgroups
37+
all_required_elements |= ancestors_of_filtered_projects
38+
end
39+
3640
total_count = [all_required_elements.size, paginator.total_count].max
3741

3842
Kaminari.paginate_array(all_required_elements, total_count: total_count)
@@ -49,8 +53,11 @@ def children
4953
end
5054

5155
def paginator
52-
@paginator ||= Gitlab::MultiCollectionPaginator.new(subgroups, projects,
53-
per_page: params[:per_page])
56+
@paginator ||= Gitlab::MultiCollectionPaginator.new(
57+
subgroups,
58+
projects.with_route,
59+
per_page: params[:per_page]
60+
)
5461
end
5562

5663
def direct_child_groups
@@ -94,15 +101,21 @@ def subgroups_matching_filter
94101
#
95102
# So when searching 'project', on the 'subgroup' page we want to preload
96103
# 'nested-group' but not 'subgroup' or 'root'
97-
def ancestors_for_groups(base_for_ancestors)
98-
Gitlab::GroupHierarchy.new(base_for_ancestors)
104+
def ancestors_of_groups(base_for_ancestors)
105+
group_ids = base_for_ancestors.except(:select, :sort).select(:id)
106+
Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
99107
.base_and_ancestors(upto: parent_group.id)
100108
end
101109

102-
def ancestors_for_projects
110+
def ancestors_of_filtered_projects
103111
projects_to_load_ancestors_of = projects.where.not(namespace: parent_group)
104112
groups_to_load_ancestors_of = Group.where(id: projects_to_load_ancestors_of.select(:namespace_id))
105-
ancestors_for_groups(groups_to_load_ancestors_of)
113+
ancestors_of_groups(groups_to_load_ancestors_of)
114+
.with_selects_for_list(archived: params[:archived])
115+
end
116+
117+
def ancestors_of_filtered_subgroups
118+
ancestors_of_groups(subgroups)
106119
.with_selects_for_list(archived: params[:archived])
107120
end
108121

@@ -112,7 +125,7 @@ def subgroups
112125
# When filtering subgroups, we want to find all matches withing the tree of
113126
# descendants to show to the user
114127
groups = if params[:filter]
115-
ancestors_for_groups(subgroups_matching_filter)
128+
subgroups_matching_filter
116129
else
117130
direct_child_groups
118131
end
@@ -121,8 +134,10 @@ def subgroups
121134
end
122135

123136
def direct_child_projects
124-
GroupProjectsFinder.new(group: parent_group, current_user: current_user, params: params)
125-
.execute
137+
GroupProjectsFinder.new(group: parent_group,
138+
current_user: current_user,
139+
options: { only_owned: true },
140+
params: params).execute
126141
end
127142

128143
# Finds all projects nested under `parent_group` or any of its descendant
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: Fix issues when rendering groups and their children
3+
merge_request: 16584
4+
author:
5+
type: fixed

spec/controllers/dashboard/groups_controller_spec.rb

+20
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,24 @@
2020

2121
expect(assigns(:groups)).to contain_exactly(member_of_group)
2222
end
23+
24+
context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do
25+
let!(:top_level_result) { create(:group, name: 'chef-top') }
26+
let!(:top_level_a) { create(:group, name: 'top-a') }
27+
let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) }
28+
let!(:other_group) { create(:group, name: 'other') }
29+
30+
before do
31+
top_level_result.add_master(user)
32+
top_level_a.add_master(user)
33+
end
34+
35+
it 'renders only groups the user is a member of when searching hierarchy correctly' do
36+
get :index, filter: 'chef', format: :json
37+
38+
expect(response).to have_gitlab_http_status(200)
39+
all_groups = [top_level_result, top_level_a, sub_level_result_a]
40+
expect(assigns(:groups)).to contain_exactly(*all_groups)
41+
end
42+
end
2343
end

spec/controllers/groups/children_controller_spec.rb

+24
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,30 @@
160160
expect(json_response).to eq([])
161161
end
162162

163+
it 'succeeds if multiple pages contain matching subgroups' do
164+
create(:group, parent: group, name: 'subgroup-filter-1')
165+
create(:group, parent: group, name: 'subgroup-filter-2')
166+
167+
# Creating the group-to-nest first so it would be loaded into the
168+
# relation first before it's parents, this is what would cause the
169+
# crash in: https://gitlab.com/gitlab-org/gitlab-ce/issues/40785.
170+
#
171+
# If we create the parent groups first, those would be loaded into the
172+
# collection first, and the pagination would cut off the actual search
173+
# result. In this case the hierarchy can be rendered without crashing,
174+
# it's just incomplete.
175+
group_to_nest = create(:group, parent: group, name: 'subsubgroup-filter-3')
176+
subgroup = create(:group, parent: group)
177+
3.times do |i|
178+
subgroup = create(:group, parent: subgroup)
179+
end
180+
group_to_nest.update!(parent: subgroup)
181+
182+
get :index, group_id: group.to_param, filter: 'filter', per_page: 3, format: :json
183+
184+
expect(response).to have_gitlab_http_status(200)
185+
end
186+
163187
it 'includes pagination headers' do
164188
2.times { |i| create(:group, :public, parent: public_subgroup, name: "filterme#{i}") }
165189

spec/finders/group_descendants_finder_spec.rb

+20
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@
3535
expect(finder.execute).to contain_exactly(project)
3636
end
3737

38+
it 'does not include projects shared with the group' do
39+
project = create(:project, namespace: group)
40+
other_project = create(:project)
41+
other_project.project_group_links.create(group: group,
42+
group_access: ProjectGroupLink::MASTER)
43+
44+
expect(finder.execute).to contain_exactly(project)
45+
end
46+
3847
context 'when archived is `true`' do
3948
let(:params) { { archived: 'true' } }
4049

@@ -189,6 +198,17 @@
189198
expect(finder.execute).to contain_exactly(subgroup, matching_project)
190199
end
191200

201+
context 'with a small page size' do
202+
let(:params) { { filter: 'test', per_page: 1 } }
203+
204+
it 'contains all the ancestors of a matching subgroup regardless the page size' do
205+
subgroup = create(:group, :private, parent: group)
206+
matching = create(:group, :private, name: 'testgroup', parent: subgroup)
207+
208+
expect(finder.execute).to contain_exactly(subgroup, matching)
209+
end
210+
end
211+
192212
it 'does not include the parent itself' do
193213
group.update!(name: 'test')
194214

0 commit comments

Comments
 (0)