Commit 43bcc1bf authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch '7328-reorder-epics-api' into 'master'

Add API for reordering child epics

Closes #7328

See merge request gitlab-org/gitlab-ee!9781
parents fadfbe18 c81c9025
......@@ -21,7 +21,7 @@ GET /groups/:id/epics/:epic_iid/epics
| Attribute | Type | Required | Description |
| ---------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `epic_iid` | integer/string | yes | The internal ID of the epic. |
| `epic_iid` | integer | yes | The internal ID of the epic. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/epics/
......@@ -73,8 +73,8 @@ POST /groups/:id/epics/:epic_iid/epics
| Attribute | Type | Required | Description |
| --------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `epic_iid` | integer/string | yes | The internal ID of the epic. |
| `child_epic_id` | integer/string | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
| `epic_iid` | integer | yes | The internal ID of the epic. |
| `child_epic_id` | integer | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
```bash
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/epics/6
......@@ -113,9 +113,62 @@ Example response:
}
```
## Delete an epic parent
## Re-order a child epic
Removes an epic - epic association.
```
PUT /groups/:id/epics/:epic_iid/epics/:child_epic_id
```
| Attribute | Type | Required | Description |
| ---------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `epic_iid` | integer | yes | The internal ID of the epic. |
| `child_epic_id` | integer | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
| `move_before_id` | integer | no | The global ID of a sibling epic that should be placed before the child epic. |
| `move_after_id` | integer | no | The global ID of a sibling epic that should be placed after the child epic. |
```bash
curl --header PUT "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/4/epics/5
```
Example response:
```json
[
{
"id": 29,
"iid": 6,
"group_id": 1,
"parent_id": 5,
"title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"author": {
"id": 10,
"name": "Lu Mayer",
"username": "kam",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
"web_url": "http://localhost:3001/kam"
},
"start_date": null,
"start_date_is_fixed": false,
"start_date_fixed": null,
"start_date_from_milestones": null,
"end_date": "2018-07-31",
"due_date": "2018-07-31",
"due_date_is_fixed": false,
"due_date_fixed": null,
"due_date_from_milestones": "2018-07-31",
"created_at": "2018-07-17T13:36:22.770Z",
"updated_at": "2018-07-18T12:22:05.239Z",
"labels": []
}
]
```
## Unassign a child epic
Unassigns a child epic from a parent epic.
```
DELETE /groups/:id/epics/:epic_iid/epics/:child_epic_id
......@@ -124,8 +177,8 @@ DELETE /groups/:id/epics/:epic_iid/epics/:child_epic_id
| Attribute | Type | Required | Description |
| --------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `epic_iid` | integer/string | yes | The internal ID of the epic. |
| `child_epic_id` | integer/string | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
| `epic_iid` | integer | yes | The internal ID of the epic. |
| `child_epic_id` | integer | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
```bash
curl --header DELETE "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/4/epics/5
......
---
title: Add API for reordering child epics
merge_request: 9781
author:
type: added
......@@ -19,6 +19,14 @@ module API
end
end
def child_epics
EpicsFinder.new(current_user, {
parent_id: epic.id,
group_id: user_group.id,
sort: 'relative_position'
}).execute
end
params :child_epic_id do
# Unique ID should be used because epics from other groups can be assigned as child.
requires :child_epic_id, type: Integer, desc: 'The global ID of the epic that will be assigned as child'
......@@ -29,6 +37,7 @@ module API
requires :id, type: String, desc: 'The ID of a group'
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get related epics' do
success EE::API::Entities::Epic
......@@ -36,8 +45,6 @@ module API
get ':id/(-/)epics/:epic_iid/epics' do
authorize_can_read!
child_epics = EpicsFinder.new(current_user, parent_id: epic.id, group_id: user_group.id).execute
present child_epics, with: EE::API::Entities::Epic
end
......@@ -72,6 +79,26 @@ module API
present updated_epic, with: EE::API::Entities::Epic
end
desc 'Reorder child epics'
params do
use :child_epic_id
optional :move_before_id, type: Integer, desc: 'The id of the epic that should be positioned before the child epic'
optional :move_after_id, type: Integer, desc: 'The id of the epic that should be positioned after the child epic'
end
put ':id/(-/)epics/:epic_iid/epics/:child_epic_id' do
authorize_can_admin!
update_params = params.slice(:move_before_id, :move_after_id)
result = ::EpicLinks::UpdateService.new(child_epic, current_user, update_params).execute
if result[:status] == :success
present child_epics, with: EE::API::Entities::Epic
else
render_api_error!(result[:message], result[:http_status])
end
end
end
end
end
......@@ -11,21 +11,25 @@ describe API::EpicLinks do
it 'returns 403 when epics feature is disabled' do
group.add_developer(user)
get api(url, user)
subject
expect(response).to have_gitlab_http_status(403)
end
it 'returns 401 unauthorized error for non authenticated user' do
get api(url)
context 'unauthenticated user' do
let(:user) { nil }
expect(response).to have_gitlab_http_status(401)
it 'returns 401 unauthorized error' do
subject
expect(response).to have_gitlab_http_status(401)
end
end
it 'returns 404 not found error for a user without permissions to see the group' do
group.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
get api(url, user)
subject
expect(response).to have_gitlab_http_status(404)
end
......@@ -34,6 +38,8 @@ describe API::EpicLinks do
describe 'GET /groups/:id/epics/:epic_iid/epics' do
let(:url) { "/groups/#{group.path}/epics/#{epic.iid}/epics" }
subject { get api(url, user) }
it_behaves_like 'user does not have access'
context 'when epics feature is enabled' do
......@@ -41,24 +47,26 @@ describe API::EpicLinks do
stub_licensed_features(epics: true)
end
let!(:child_epic1) { create(:epic, group: group, parent: epic) }
let!(:child_epic2) { create(:epic, group: group, parent: epic) }
let!(:child_epic1) { create(:epic, group: group, parent: epic, relative_position: 200) }
let!(:child_epic2) { create(:epic, group: group, parent: epic, relative_position: 100) }
it 'returns 200 status' do
get api(url, user)
subject
epics = JSON.parse(response.body)
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/epics', dir: 'ee')
expect(epics.map { |epic| epic["id"] }).to match_array([child_epic1.id, child_epic2.id])
expect(epics.map { |epic| epic["id"] }).to eq([child_epic2.id, child_epic1.id])
end
end
end
describe 'POST /groups/:id/epics/:epic_iid/epics' do
let(:child_epic) { create(:epic, group: group) }
let(:url) { "/groups/#{group.path}/epics/#{epic.iid}/epics" }
let(:url) { "/groups/#{group.path}/epics/#{epic.iid}/epics/#{child_epic.id}" }
subject { post api(url, user) }
it_behaves_like 'user does not have access'
......@@ -71,7 +79,7 @@ describe API::EpicLinks do
it 'returns 403' do
group.add_guest(user)
post api("#{url}/#{child_epic.id}", user)
subject
expect(response).to have_gitlab_http_status(403)
end
......@@ -81,7 +89,7 @@ describe API::EpicLinks do
it 'returns 201 status' do
group.add_developer(user)
post api("#{url}/#{child_epic.id}", user)
subject
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/epic', dir: 'ee')
......@@ -96,7 +104,7 @@ describe API::EpicLinks do
it 'returns 404 status' do
group.add_developer(user)
post api(url, user), params: { child_epic_id: child_epic.id }
subject
expect(response).to have_gitlab_http_status(404)
end
......@@ -104,9 +112,53 @@ describe API::EpicLinks do
end
end
describe 'PUT /groups/:id/epics/:epic_iid/epics/:child_epic_id' do
let!(:child_epic) { create(:epic, group: group, parent: epic, relative_position: 100) }
let!(:sibling_1) { create(:epic, group: group, parent: epic, relative_position: 200) }
let!(:sibling_2) { create(:epic, group: group, parent: epic, relative_position: 300) }
let(:url) { "/groups/#{group.path}/epics/#{epic.iid}/epics/#{child_epic.id}" }
subject { put api(url, user), params: { move_before_id: sibling_1.id, move_after_id: sibling_2.id } }
it_behaves_like 'user does not have access'
context 'when epics are enabled' do
before do
stub_licensed_features(epics: true)
end
context 'when user has permissions to reorder epics' do
before do
group.add_developer(user)
end
it 'returns status 200' do
subject
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/epics', dir: 'ee')
expect(json_response.map { |epic| epic['id'] }).to eq([sibling_1.id, child_epic.id, sibling_2.id])
end
end
context 'when user does not have permissions to reorder epics' do
it 'returns status 403' do
group.add_guest(user)
subject
expect(response).to have_gitlab_http_status(403)
end
end
end
end
describe 'DELETE /groups/:id/epics/:epic_iid/epics' do
let!(:child_epic) { create(:epic, group: group, parent: epic)}
let(:url) { "/groups/#{group.path}/epics/#{epic.iid}/epics" }
let(:url) { "/groups/#{group.path}/epics/#{epic.iid}/epics/#{child_epic.id}" }
subject { delete api(url, user) }
it_behaves_like 'user does not have access'
......@@ -119,7 +171,7 @@ describe API::EpicLinks do
it 'returns 403' do
group.add_guest(user)
delete api("#{url}/#{child_epic.id}", user)
subject
expect(response).to have_gitlab_http_status(403)
end
......@@ -129,7 +181,7 @@ describe API::EpicLinks do
it 'returns 200 status' do
group.add_developer(user)
delete api("#{url}/#{child_epic.id}", user)
subject
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/epic', dir: 'ee')
......
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