Commit ab4aba91 authored by Jarka Kadlecová's avatar Jarka Kadlecová

Add API for epics

parent f88c2fe0
---
title: Add API for epics
merge_request:
author:
type: added
......@@ -18,6 +18,7 @@ following locations:
- [Deployments](deployments.md)
- [Deploy Keys](deploy_keys.md)
- [Environments](environments.md)
- [Epics](epics.md)
- [Epic Issues](epic_issues.md)
- [Events](events.md)
- [Feature flags](features.md)
......
# Epics API
Every API call to epic_issues must be authenticated.
If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
Epics are available only in EEU. If epics feature is not available a `403` status code will be returned.
## List epics for a group
Gets all epics of the requested group.
```
GET /groups/:id/-/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 |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics
```
Example response:
```json
[
{
"id": 29,
"iid": 4,
"group_id": 7,
"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,
"end_date": null
}
]
```
## Single epic
Gets a single epic
```
GET /groups/:id/-/epics/:epic_iid
```
| 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. |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5
```
Example response:
```json
{
"id": 30,
"iid": 5,
"group_id": 7,
"title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.",
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"author":{
"id": 7,
"name": "Pamella Huel",
"username": "arnita",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
"web_url": "http://localhost:3001/arnita"
},
"start_date": null,
"end_date": null
}
```
## New epic
Creates a new epic
```
POST /groups/:id/-/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 |
| `title` | string | yes | The title of the epic |
| `description` | string | no | The description of the epic |
| `start_date` | string | no | The start date of the epic |
| `end_date` | string. | no | The end date of the epic |
```bash
curl --header POST "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics?title=Epic&description=Epic%20description
```
Example response:
```json
{
"id": 33,
"iid": 6,
"group_id": 7,
"title": "Epic",
"description": "Epic description",
"author": {
"name" : "Alexandra Bashirian",
"avatar_url" : null,
"state" : "active",
"web_url" : "https://gitlab.example.com/eileen.lowe",
"id" : 18,
"username" : "eileen.lowe"
},
"start_date": null,
"end_date": null
}
```
## Update epic
Updates an epic
```
PUT /groups/:id/-/epics/:epic_iid
```
| 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 |
| `title` | string | no | The title of an epic |
| `description` | string | no | The description of an epic |
| `start_date` | string | no | The start date of an epic |
| `end_date` | string. | no | The end date of an epic |
```bash
curl --header PUT "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5?title=New%20Title
```
Example response:
```json
{
"id": 33,
"iid": 6,
"group_id": 7,
"title": "New Title",
"description": "Epic description",
"author": {
"name" : "Alexandra Bashirian",
"avatar_url" : null,
"state" : "active",
"web_url" : "https://gitlab.example.com/eileen.lowe",
"id" : 18,
"username" : "eileen.lowe"
},
"start_date": null,
"end_date": null
}
```
## Delete epic
Deletes an epic
```
DELETE /groups/:id/-/epics/:epic_iid
```
| 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. |
```bash
curl --header DELETE "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics/5?title=New%20Title
```
module API
class Epics < Grape::API
before do
authenticate!
authorize_epics_feature!
end
helpers do
def authorize_epics_feature!
forbidden! unless user_group.feature_available?(:epics)
end
def authorize_can_read!
authorize!(:read_epic, epic)
end
def authorize_can_admin!
authorize!(:admin_epic, epic)
end
def authorize_can_create!
authorize!(:admin_epic, user_group)
end
def authorize_can_destroy!
authorize!(:destroy_epic, epic)
end
def epic
@epic ||= user_group.epics.find_by(iid: params[:epic_iid])
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get epics for the group' do
success Entities::Epic
end
get ':id/-/epics' do
present user_group.epics, with: Entities::Epic
end
desc 'Get details of an epic' do
success Entities::Epic
end
params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
end
get ':id/-/epics/:epic_iid' do
authorize_can_read!
present epic, with: Entities::Epic
end
desc 'Create a new epic' do
success Entities::Epic
end
params do
requires :title, type: String, desc: 'The title of an epic'
optional :description, type: String, desc: 'The description of an epic'
optional :start_date, type: String, desc: 'The start date of an epic'
optional :end_date, type: String, desc: 'The end date of an epic'
end
post ':id/-/epics' do
authorize_can_create!
epic = ::Epics::CreateService.new(user_group, current_user, declared_params(include_missing: false)).execute
if epic.valid?
present epic, with: Entities::Epic
else
render_validation_error!(epic)
end
end
desc 'Update an epic' do
success Entities::Epic
end
params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
optional :title, type: String, desc: 'The title of an epic'
optional :description, type: String, desc: 'The description of an epic'
optional :start_date, type: String, desc: 'The start date of an epic'
optional :end_date, type: String, desc: 'The end date of an epic'
at_least_one_of :title, :description, :start_date, :end_date
end
put ':id/-/epics/:epic_iid' do
authorize_can_admin!
update_params = declared_params(include_missing: false)
update_params.delete(:epic_iid)
result = ::Epics::UpdateService.new(nil, current_user, update_params).execute(epic)
if result.valid?
present result, with: Entities::Epic
else
render_validation_error!(result)
end
end
desc 'Destroy an epic' do
success Entities::Epic
end
params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
end
delete ':id/-/epics/:epic_iid' do
authorize_can_destroy!
Issuable::DestroyService.new(nil, current_user).execute(epic)
end
end
end
end
......@@ -125,6 +125,7 @@ module API
mount ::API::Deployments
mount ::API::Environments
mount ::API::EpicIssues
mount ::API::Epics
mount ::API::Events
mount ::API::Features
mount ::API::Files
......
......@@ -494,6 +494,7 @@ module API
class Epic < Grape::Entity
expose :id
expose :iid
expose :group_id
expose :title
expose :description
expose :author, using: Entities::UserBasic
......
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
"group_id": { "type": "integer" },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"author": {
"type": ["object", "null"],
"properties": {
"name": { "type": "string" },
"username": { "type": "string" },
"id": { "type": "integer" },
"state": { "type": "string" },
"avatar_url": { "type": "uri" },
"web_url": { "type": "uri" }
},
"additionalProperties": false
},
"start_date": { "type": ["string", "null"] },
"end_date": { "type": ["string", "null"] }
},
"required": [
"id", "iid", "group_id", "title"
],
"additionalProperties": false
}
......@@ -14,6 +14,7 @@
"id": { "type": "integer" },
"iid": { "type": "integer" },
"title": { "type": "string" },
"group_id": { "type": "integer" },
"description": { "type": ["string", "null"] },
"author": { "type": ["object", "null"] },
"start_date": { "type": ["string", "null"] },
......
{
"type": "array",
"items": {
"type": "object",
"properties": {
"$ref": "./epic.json"
}
}
}
require 'spec_helper'
describe API::Epics do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) }
let(:epic) { create(:epic, group: group) }
let(:params) { nil }
shared_examples 'error requests' do
context 'when epics feature is disabled' do
it 'returns 403 forbidden error' do
group.add_developer(user)
get api(url, user), params
expect(response).to have_gitlab_http_status(403)
end
context 'when epics feature is enabled' do
before do
stub_licensed_features(epics: true)
end
it 'returns 401 unauthorized error for non authenticated user' do
get api(url), params
expect(response).to have_gitlab_http_status(401)
end
it 'returns 404 not found error for a user without permissions to see the group' do
project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
group.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
get api(url, user), params
expect(response).to have_gitlab_http_status(404)
end
end
end
end
describe 'GET /groups/:id/-/epics' do
let(:url) { "/groups/#{group.path}/-/epics" }
it_behaves_like 'error requests'
context 'when the request is correct' do
before do
stub_licensed_features(epics: true)
get api(url, user)
end
it 'returns 200 status' do
expect(response).to have_gitlab_http_status(200)
end
it 'matches the response schema' do
expect(response).to match_response_schema('public_api/v4/epics', dir: 'ee')
end
end
end
describe 'GET /groups/:id/-/epics/:epic_iid' do
let(:url) { "/groups/#{group.path}/-/epics/#{epic.iid}" }
it_behaves_like 'error requests'
context 'when the request is correct' do
before do
stub_licensed_features(epics: true)
get api(url, user)
end
it 'returns 200 status' do
expect(response).to have_gitlab_http_status(200)
end
it 'matches the response schema' do
expect(response).to match_response_schema('public_api/v4/epic', dir: 'ee')
end
end
end
describe 'POST /groups/:id/-/epics' do
let(:url) { "/groups/#{group.path}/-/epics" }
let(:params) { { title: 'new epic', description: 'epic description' } }
it_behaves_like 'error requests'
context 'when epics feature is enabled' do
before do
stub_licensed_features(epics: true)
end
context 'when required parameter is missing' do
it 'returns 400' do
group.add_developer(user)
post api(url, user), description: 'epic description'
expect(response).to have_gitlab_http_status(400)
end
end
context 'when the request is correct' do
before do
group.add_developer(user)
post api(url, user), params
end
it 'returns 201 status' do
expect(response).to have_gitlab_http_status(201)
end
it 'matches the response schema' do
expect(response).to match_response_schema('public_api/v4/epic', dir: 'ee')
end
it 'creates a new epic' do
epic = Epic.last
expect(epic.title).to eq('new epic')
expect(epic.description).to eq('epic description')
end
end
end
end
describe 'PUT /groups/:id/-/epics/:epic_iid' do
let(:url) { "/groups/#{group.path}/-/epics/#{epic.iid}" }
let(:params) { { title: 'new title', description: 'new description' } }
it_behaves_like 'error requests'
context 'when epics feature is enabled' do
before do
stub_licensed_features(epics: true)
end
context 'when a user does not have permissions to create an epic' do
it 'returns 403 forbidden error' do
put api(url, user), params
expect(response).to have_gitlab_http_status(403)
end
end
context 'when no param sent' do
it 'returns 400' do
group.add_developer(user)
put api(url, user)
expect(response).to have_gitlab_http_status(400)
end
end
context 'when the request is correct' do
before do
group.add_developer(user)
put api(url, user), params
end
it 'returns 200 status' do
expect(response).to have_gitlab_http_status(200)
end
it 'matches the response schema' do
expect(response).to match_response_schema('public_api/v4/epic', dir: 'ee')
end
it 'updates the epic' do
result = epic.reload
expect(result.title).to eq('new title')
expect(result.description).to eq('new description')
end
end
end
end
describe 'DELETE /groups/:id/-/epics/:epic_iid' do
let(:url) { "/groups/#{group.path}/-/epics/#{epic.iid}" }
it_behaves_like 'error requests'
context 'when epics feature is enabled' do
before do
stub_licensed_features(epics: true)
end
context 'when a user does not have permissions to destroy an epic' do
it 'returns 403 forbidden error' do
group.add_developer(user)
delete api(url, user)
expect(response).to have_gitlab_http_status(403)
end
end
context 'when the request is correct' do
before do
group.add_owner(user)
end
it 'returns 204 status' do
delete api(url, user)
expect(response).to have_gitlab_http_status(204)
end
it 'removes an epic' do
epic
expect { delete api(url, user) }.to change { Epic.count }.from(1).to(0)
end
end
end
end
end
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