Commit a90add3e authored by James Edwards-Jones's avatar James Edwards-Jones

Add top_level_only option to Groups API

Allows subgroups to be excluded when listing groups.

Works by passing `[nil]` in to GroupsFinder so the param isn't skipped
due to being falsey.
parent 3a51227b
---
title: Groups API has top_level_only option to exclude subgroups
merge_request: 32870
author:
type: added
...@@ -20,6 +20,7 @@ Parameters: ...@@ -20,6 +20,7 @@ Parameters:
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
| `owned` | boolean | no | Limit to groups explicitly owned by the current user | | `owned` | boolean | no | Limit to groups explicitly owned by the current user |
| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md) | | `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md) |
| `top_level_only` | boolean | no | Limit to top level groups, excluding all subgroups |
```plaintext ```plaintext
GET /groups GET /groups
......
...@@ -23,13 +23,20 @@ module API ...@@ -23,13 +23,20 @@ module API
optional :order_by, type: String, values: %w[name path id], default: 'name', desc: 'Order by name, path or id' optional :order_by, type: String, values: %w[name path id], default: 'name', desc: 'Order by name, path or id'
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user' optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user'
optional :top_level_only, type: Boolean, desc: 'Only include top level groups'
use :pagination use :pagination
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_groups(params, parent_id = nil) def find_groups(params, parent_id = nil)
find_params = params.slice(:all_available, :custom_attributes, :owned, :min_access_level) find_params = params.slice(:all_available, :custom_attributes, :owned, :min_access_level)
find_params[:parent] = find_group!(parent_id) if parent_id
find_params[:parent] = if params[:top_level_only]
[nil]
elsif parent_id
find_group!(parent_id)
end
find_params[:all_available] = find_params[:all_available] =
find_params.fetch(:all_available, current_user&.can_read_all_resources?) find_params.fetch(:all_available, current_user&.can_read_all_resources?)
......
...@@ -74,6 +74,12 @@ describe GroupsFinder do ...@@ -74,6 +74,12 @@ describe GroupsFinder do
let!(:internal_subgroup) { create(:group, :internal, parent: parent_group) } let!(:internal_subgroup) { create(:group, :internal, parent: parent_group) }
let!(:private_subgroup) { create(:group, :private, parent: parent_group) } let!(:private_subgroup) { create(:group, :private, parent: parent_group) }
context 'with [nil] parent' do
it 'returns only top-level groups' do
expect(described_class.new(user, parent: [nil]).execute).to contain_exactly(parent_group)
end
end
context 'without a user' do context 'without a user' do
it 'only returns parent and public subgroups' do it 'only returns parent and public subgroups' do
expect(described_class.new(nil).execute).to contain_exactly(parent_group, public_subgroup) expect(described_class.new(nil).execute).to contain_exactly(parent_group, public_subgroup)
......
...@@ -231,6 +231,27 @@ describe API::Groups do ...@@ -231,6 +231,27 @@ describe API::Groups do
end end
end end
context "when using top_level_only" do
let(:top_level_group) { create(:group, name: 'top-level-group') }
let(:subgroup) { create(:group, :nested, name: 'subgroup') }
let(:response_groups) { json_response.map { |group| group['name'] } }
before do
top_level_group.add_owner(user1)
subgroup.add_owner(user1)
end
it "doesn't return subgroups" do
get api("/groups", user1), params: { top_level_only: true }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to include(top_level_group.name)
expect(response_groups).not_to include(subgroup.name)
end
end
context "when using sorting" do context "when using sorting" do
let(:group3) { create(:group, name: "a#{group1.name}", path: "z#{group1.path}") } let(:group3) { create(:group, name: "a#{group1.name}", path: "z#{group1.path}") }
let(:group4) { create(:group, name: "same-name", path: "y#{group1.path}") } let(:group4) { create(:group, name: "same-name", path: "y#{group1.path}") }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment