Commit c3f71ada authored by Manoj M J's avatar Manoj M J

Introduce API to transfer groups to a new parent group

This MR introduces a new API
to transfer a group to a new parent group.

This API can also be used to promote a subgroup
into a root group.

Changelog: added
parent 447acc66
......@@ -850,6 +850,32 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/groups/4/projects/56"
```
## Transfer a group to a new parent group / Turn a subgroup to a top-level group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23831) in GitLab 14.6.
Transfer a group to a new parent group or turn a subgroup to a top-level group. Available to administrators and users:
- With the Owner role for the group to transfer.
- With permission to [create a subgroup](../user/group/subgroups/index.md#creating-a-subgroup) in the new parent group if transferring a group.
- With [permission to create a top-level group](../administration/user_settings.md#prevent-users-from-creating-top-level-groups) if turning a subgroup into a top-level group.
```plaintext
POST /groups/:id/transfer
```
Parameters:
| Attribute | Type | Required | Description |
| ------------ | -------------- | -------- | ----------- |
| `id` | integer | yes | ID of the group to transfer. |
| `group_id` | integer | no | ID of the new parent group. When not specified, the group to transfer is instead turned into a top-level group. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/groups/4/transfer?group_id=7"
```
## Update group
Updates the project group. Only available to group owners and administrators.
......
......@@ -382,6 +382,28 @@ module API
end
end
desc 'Transfer a group to a new parent group or promote a subgroup to a root group'
params do
optional :group_id, type: Integer,
desc: 'The ID of the target group to which the group needs to be transferred to.'\
'If not provided, the source group will be promoted to a root group.'
end
post ':id/transfer' do
group = find_group!(params[:id])
authorize! :admin_group, group
new_parent_group = find_group!(params[:group_id]) if params[:group_id].present?
service = ::Groups::TransferService.new(group, current_user)
if service.execute(new_parent_group)
group.preload_shared_group_links
present group, with: Entities::GroupDetail, current_user: current_user
else
render_api_error!(service.error, 400)
end
end
desc 'Share a group with a group' do
success Entities::GroupDetail
end
......
......@@ -1943,6 +1943,116 @@ RSpec.describe API::Groups do
end
end
describe 'POST /groups/:id/transfer' do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:new_parent_group) { create(:group, :private) }
let_it_be_with_reload(:group) { create(:group, :nested, :private) }
before do
new_parent_group.add_owner(user)
group.add_owner(user)
end
def make_request(user)
post api("/groups/#{group.id}/transfer", user), params: params
end
context 'when promoting a subgroup to a root group' do
shared_examples_for 'promotes the subgroup to a root group' do
it 'returns success' do
make_request(user)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['parent_id']).to be_nil
end
end
context 'when no group_id is specified' do
let(:params) {}
it_behaves_like 'promotes the subgroup to a root group'
end
context 'when group_id is specified as blank' do
let(:params) { { group_id: '' } }
it_behaves_like 'promotes the subgroup to a root group'
end
context 'when the group is already a root group' do
let(:group) { create(:group) }
let(:params) { { group_id: '' } }
it 'returns error' do
make_request(user)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('Transfer failed: Group is already a root group.')
end
end
end
context 'when transferring a subgroup to a different group' do
let(:params) { { group_id: new_parent_group.id } }
context 'when the user does not have admin rights to the group being transferred' do
it 'forbids the operation' do
developer_user = create(:user)
group.add_developer(developer_user)
make_request(developer_user)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when the user does not have access to the new parent group' do
it 'fails with 404' do
user_without_access_to_new_parent_group = create(:user)
group.add_owner(user_without_access_to_new_parent_group)
make_request(user_without_access_to_new_parent_group)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the ID of a non-existent group is mentioned as the new parent group' do
let(:params) { { group_id: non_existing_record_id } }
it 'fails with 404' do
make_request(user)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the transfer fails due to an error' do
before do
expect_next_instance_of(::Groups::TransferService) do |service|
expect(service).to receive(:proceed_to_transfer).and_raise(Gitlab::UpdatePathError, 'namespace directory cannot be moved')
end
end
it 'returns error' do
make_request(user)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('Transfer failed: namespace directory cannot be moved')
end
end
context 'when the transfer succceds' do
it 'returns success' do
make_request(user)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['parent_id']).to eq(new_parent_group.id)
end
end
end
end
it_behaves_like 'custom attributes endpoints', 'groups' do
let(:attributable) { group1 }
let(:other_attributable) { group2 }
......
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