diff --git a/app/finders/group_children_finder.rb b/app/finders/group_children_finder.rb new file mode 100644 index 00000000000..d95dfa2a877 --- /dev/null +++ b/app/finders/group_children_finder.rb @@ -0,0 +1,54 @@ +class GroupChildrenFinder + 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 + end + + def execute + Kaminari.paginate_array(children) + end + + # This allows us to fetch only the count without loading the objects. Unless + # the objects were already loaded. + def total_count + @total_count ||= if defined?(@children) + children.size + else + child_groups.count + projects.count + end + end + + private + + def children + @children ||= child_groups + projects + end + + def child_groups + return Group.none unless Group.supports_nested_groups? + return Group.none unless can?(current_user, :read_group, parent_group) + + groups = GroupsFinder.new(current_user, + parent: parent_group, + all_available: true, + all_children_for_parent: params[:filter_groups].present?).execute + + groups = groups.search(params[:filter]) if params[:filter].present? + groups = groups.includes(:route).includes(:children) + groups.sort(params[:sort]) + end + + def projects + return Project.none unless can?(current_user, :read_group, parent_group) + + projects = GroupProjectsFinder.new(group: parent_group, params: params, current_user: current_user).execute + projects = projects.includes(:route) + projects = projects.search(params[:filter]) if params[:filter].present? + projects.sort(params[:sort]) + end +end diff --git a/spec/finders/group_children_finder_spec.rb b/spec/finders/group_children_finder_spec.rb new file mode 100644 index 00000000000..afd96e27a1d --- /dev/null +++ b/spec/finders/group_children_finder_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe GroupChildrenFinder do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:params) { {} } + subject(:finder) { described_class.new(user, parent_group: group, params: params) } + + before do + group.add_owner(user) + end + + describe '#execute' do + it 'includes projects' do + project = create(:project, namespace: group) + + expect(finder.execute).to contain_exactly(project) + end + + context 'with a filter' do + let(:params) { { filter: 'test' } } + + it 'includes only projects matching the filter' do + _other_project = create(:project, namespace: group) + matching_project = create(:project, namespace: group, name: 'testproject') + + expect(finder.execute).to contain_exactly(matching_project) + end + end + end + + context 'with nested groups', :nested_groups do + let!(:project) { create(:project, namespace: group) } + let!(:subgroup) { create(:group, parent: group) } + + describe '#execute' do + it 'contains projects and subgroups' do + expect(finder.execute).to contain_exactly(subgroup, project) + end + + context 'with a filter' do + let(:params) { { filter: 'test' } } + + it 'contains only matching projects and subgroups' do + matching_project = create(:project, namespace: group, name: 'Testproject') + matching_subgroup = create(:group, name: 'testgroup', parent: group) + + expect(finder.execute).to contain_exactly(matching_subgroup, matching_project) + end + end + end + + describe '#total_count' do + it 'counts the array children were already loaded' do + finder.instance_variable_set(:@children, [double]) + + expect(finder).not_to receive(:child_groups) + expect(finder).not_to receive(:projects) + + expect(finder.total_count).to eq(1) + end + + it 'performs a count without loading children when they are not loaded yet' do + expect(finder).to receive(:child_groups).and_call_original + expect(finder).to receive(:projects).and_call_original + + expect(finder.total_count).to eq(2) + end + end + end +end