Commit b8a83a15 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'epic_links' into 'master'

Add _links to epics API

Closes #230586

See merge request gitlab-org/gitlab!37395
parents 0489d9c0 298aa612
......@@ -92,7 +92,7 @@ Example response:
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"state": "opened",
"confidential": "false",
"web_url": "http://localhost:3001/groups/test/-/epics/4",
"web_url": "http://gitlab.example.com/groups/test/-/epics/4",
"reference": "&4",
"references": {
"short": "&4",
......@@ -105,7 +105,7 @@ Example response:
"username": "kam",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
"web_url": "http://localhost:3001/kam"
"web_url": "http://gitlab.example.com/kam"
},
"start_date": null,
"start_date_is_fixed": false,
......@@ -123,7 +123,12 @@ Example response:
"closed_at": "2018-08-18T12:22:05.239Z",
"labels": [],
"upvotes": 4,
"downvotes": 0
"downvotes": 0,
"_links":{
"self": "http://gitlab.example.com/api/v4/groups/7/epics/4",
"epic_issues": "http://gitlab.example.com/api/v4/groups/7/epics/4/issues",
"group":"http://gitlab.example.com/api/v4/groups/7"
}
},
{
"id": 50,
......@@ -133,7 +138,7 @@ Example response:
"title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"state": "opened",
"web_url": "http://localhost:3001/groups/test/sample/-/epics/4",
"web_url": "http://gitlab.example.com/groups/test/sample/-/epics/35",
"reference": "&4",
"references": {
"short": "&4",
......@@ -146,7 +151,7 @@ Example response:
"username": "kam",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
"web_url": "http://localhost:3001/kam"
"web_url": "http://gitlab.example.com/kam"
},
"start_date": null,
"start_date_is_fixed": false,
......@@ -164,7 +169,12 @@ Example response:
"closed_at": "2018-08-18T12:22:05.239Z",
"labels": [],
"upvotes": 4,
"downvotes": 0
"downvotes": 0,
"_links":{
"self": "http://gitlab.example.com/api/v4/groups/17/epics/35",
"epic_issues": "http://gitlab.example.com/api/v4/groups/17/epics/35/issues",
"group":"http://gitlab.example.com/api/v4/groups/17"
}
}
]
```
......@@ -196,7 +206,7 @@ Example response:
"title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.",
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"state": "opened",
"web_url": "http://localhost:3001/groups/test/-/epics/5",
"web_url": "http://gitlab.example.com/groups/test/-/epics/5",
"reference": "&5",
"references": {
"short": "&5",
......@@ -209,7 +219,7 @@ Example response:
"username": "arnita",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
"web_url": "http://localhost:3001/arnita"
"web_url": "http://gitlab.example.com/arnita"
},
"start_date": null,
"start_date_is_fixed": false,
......@@ -228,7 +238,12 @@ Example response:
"labels": [],
"upvotes": 4,
"downvotes": 0,
"subscribed": true
"subscribed": true,
"_links":{
"self": "http://gitlab.example.com/api/v4/groups/7/epics/5",
"epic_issues": "http://gitlab.example.com/api/v4/groups/7/epics/5/issues",
"group":"http://gitlab.example.com/api/v4/groups/7"
}
}
```
......@@ -273,7 +288,7 @@ Example response:
"description": "Epic description",
"state": "opened",
"confidential": "false",
"web_url": "http://localhost:3001/groups/test/-/epics/6",
"web_url": "http://gitlab.example.com/groups/test/-/epics/6",
"reference": "&6",
"references": {
"short": "&6",
......@@ -304,7 +319,12 @@ Example response:
"closed_at": "2018-08-18T12:22:05.239Z",
"labels": [],
"upvotes": 4,
"downvotes": 0
"downvotes": 0,
"_links":{
"self": "http://gitlab.example.com/api/v4/groups/7/epics/6",
"epic_issues": "http://gitlab.example.com/api/v4/groups/7/epics/6/issues",
"group":"http://gitlab.example.com/api/v4/groups/7"
}
}
```
......@@ -350,7 +370,7 @@ Example response:
"description": "Epic description",
"state": "opened",
"confidential": "false",
"web_url": "http://localhost:3001/groups/test/-/epics/6",
"web_url": "http://gitlab.example.com/groups/test/-/epics/6",
"reference": "&6",
"references": {
"short": "&6",
......@@ -456,9 +476,9 @@ Example response:
"username": "arnita",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
"web_url": "http://localhost:3001/arnita"
"web_url": "http://gitlab.example.com/arnita"
},
"web_url": "http://localhost:3001/groups/test/-/epics/5",
"web_url": "http://gitlab.example.com/groups/test/-/epics/5",
"reference": "&5",
"references": {
"short": "&5",
......
......@@ -134,7 +134,7 @@ Example response:
"merge_requests_count": 0,
"user_notes_count": 1,
"due_date": "2016-07-22",
"web_url": "http://example.com/my-group/my-project/issues/6",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/6",
"references": {
"short": "#6",
"relative": "my-group/my-project#6",
......@@ -151,10 +151,10 @@ Example response:
"confidential": false,
"discussion_locked": false,
"_links":{
"self":"http://example.com/api/v4/projects/1/issues/76",
"notes":"`http://example.com/`api/v4/projects/1/issues/76/notes",
"award_emoji":"http://example.com/api/v4/projects/1/issues/76/award_emoji",
"project":"http://example.com/api/v4/projects/1"
"self":"http://gitlab.example.com/api/v4/projects/1/issues/76",
"notes":"http://gitlab.example.com/api/v4/projects/1/issues/76/notes",
"award_emoji":"http://gitlab.example.com/api/v4/projects/1/issues/76/award_emoji",
"project":"http://gitlab.example.com/api/v4/projects/1"
},
"task_completion_status":{
"count":0,
......@@ -292,7 +292,7 @@ Example response:
"closed_by" : null,
"user_notes_count": 1,
"due_date": null,
"web_url": "http://example.com/my-group/my-project/issues/1",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/1",
"references": {
"short": "#1",
"relative": "my-project#1",
......@@ -309,10 +309,10 @@ Example response:
"confidential": false,
"discussion_locked": false,
"_links":{
"self":"http://example.com/api/v4/projects/4/issues/41",
"notes":"`http://example.com/`api/v4/projects/4/issues/41/notes",
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
"self":"http://gitlab.example.com/api/v4/projects/4/issues/41",
"notes":"http://gitlab.example.com/api/v4/projects/4/issues/41/notes",
"award_emoji":"http://gitlab.example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://gitlab.example.com/api/v4/projects/4"
},
"task_completion_status":{
"count":0,
......@@ -456,7 +456,7 @@ Example response:
},
"user_notes_count": 1,
"due_date": "2016-07-22",
"web_url": "http://example.com/my-group/my-project/issues/1",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/1",
"references": {
"short": "#1",
"relative": "#1",
......@@ -473,10 +473,10 @@ Example response:
"confidential": false,
"discussion_locked": false,
"_links":{
"self":"http://example.com/api/v4/projects/4/issues/41",
"notes":"`http://example.com/`api/v4/projects/4/issues/41/notes",
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
"self":"http://gitlab.example.com/api/v4/projects/4/issues/41",
"notes":"http://gitlab.example.com/api/v4/projects/4/issues/41/notes",
"award_emoji":"http://gitlab.example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://gitlab.example.com/api/v4/projects/4"
},
"task_completion_status":{
"count":0,
......@@ -581,7 +581,7 @@ Example response:
"subscribed": false,
"user_notes_count": 1,
"due_date": null,
"web_url": "http://example.com/my-group/my-project/issues/1",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/1",
"references": {
"short": "#1",
"relative": "#1",
......@@ -596,10 +596,10 @@ Example response:
"confidential": false,
"discussion_locked": false,
"_links": {
"self": "http://example.com/api/v4/projects/1/issues/2",
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
"self": "http://gitlab.example.com/api/v4/projects/1/issues/2",
"notes": "http://gitlab.example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://gitlab.example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://gitlab.example.com/api/v4/projects/1"
},
"task_completion_status":{
"count":0,
......@@ -710,7 +710,7 @@ Example response:
"subscribed" : true,
"user_notes_count": 0,
"due_date": null,
"web_url": "http://example.com/my-group/my-project/issues/14",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/14",
"references": {
"short": "#14",
"relative": "#14",
......@@ -725,10 +725,10 @@ Example response:
"confidential": false,
"discussion_locked": false,
"_links": {
"self": "http://example.com/api/v4/projects/1/issues/2",
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
"self": "http://gitlab.example.com/api/v4/projects/1/issues/2",
"notes": "http://gitlab.example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://gitlab.example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://gitlab.example.com/api/v4/projects/1"
},
"task_completion_status":{
"count":0,
......@@ -832,7 +832,7 @@ Example response:
"subscribed" : true,
"user_notes_count": 0,
"due_date": "2016-07-22",
"web_url": "http://example.com/my-group/my-project/issues/15",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/15",
"references": {
"short": "#15",
"relative": "#15",
......@@ -847,10 +847,10 @@ Example response:
"confidential": false,
"discussion_locked": false,
"_links": {
"self": "http://example.com/api/v4/projects/1/issues/2",
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
"self": "http://gitlab.example.com/api/v4/projects/1/issues/2",
"notes": "http://gitlab.example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://gitlab.example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://gitlab.example.com/api/v4/projects/1"
},
"task_completion_status":{
"count":0,
......@@ -985,7 +985,7 @@ Example response:
"web_url": "https://gitlab.example.com/solon.cremin"
},
"due_date": null,
"web_url": "http://example.com/my-group/my-project/issues/11",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/11",
"references": {
"short": "#11",
"relative": "#11",
......@@ -1000,10 +1000,10 @@ Example response:
"confidential": false,
"discussion_locked": false,
"_links": {
"self": "http://example.com/api/v4/projects/1/issues/2",
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
"self": "http://gitlab.example.com/api/v4/projects/1/issues/2",
"notes": "http://gitlab.example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://gitlab.example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://gitlab.example.com/api/v4/projects/1"
},
"task_completion_status":{
"count":0,
......@@ -1091,7 +1091,7 @@ Example response:
"web_url": "https://gitlab.example.com/solon.cremin"
},
"due_date": null,
"web_url": "http://example.com/my-group/my-project/issues/11",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/11",
"references": {
"short": "#11",
"relative": "#11",
......@@ -1106,10 +1106,10 @@ Example response:
"confidential": false,
"discussion_locked": false,
"_links": {
"self": "http://example.com/api/v4/projects/1/issues/2",
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
"self": "http://gitlab.example.com/api/v4/projects/1/issues/2",
"notes": "http://gitlab.example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://gitlab.example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://gitlab.example.com/api/v4/projects/1"
},
"task_completion_status":{
"count":0,
......@@ -1190,7 +1190,7 @@ Example response:
},
"subscribed": false,
"due_date": null,
"web_url": "http://example.com/my-group/my-project/issues/12",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/12",
"references": {
"short": "#12",
"relative": "#12",
......@@ -1297,7 +1297,7 @@ Example response:
"downvotes": 0,
"merge_requests_count": 0,
"due_date": null,
"web_url": "http://example.com/my-group/my-project/issues/10",
"web_url": "http://gitlab.example.com/my-group/my-project/issues/10",
"references": {
"short": "#10",
"relative": "#10",
......@@ -1729,7 +1729,7 @@ Example response:
"username": "user1",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
"web_url": "http://localhost/user1"
"web_url": "http://gitlab.example.com/user1"
},
{
"id": 5,
......@@ -1737,7 +1737,7 @@ Example response:
"username": "user5",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80&d=identicon",
"web_url": "http://localhost/user5"
"web_url": "http://gitlab.example.com/user5"
}
]
```
......
---
title: Add _links to epic API entity
merge_request: 37395
author:
type: added
......@@ -55,12 +55,14 @@ module API
params do
requires :epic_iid, type: Integer, desc: 'The iid of the epic'
end
get ':id/(-/)epics/:epic_iid/issues' do
authorize_can_read!
[':id/epics/:epic_iid/issues', ':id/-/epics/:epic_iid/issues'].each do |path|
get path do
authorize_can_read!
present epic.issues_readable_by(current_user),
with: EE::API::Entities::EpicIssue,
current_user: current_user
present epic.issues_readable_by(current_user),
with: EE::API::Entities::EpicIssue,
current_user: current_user
end
end
desc 'Assign an issue to the epic' do
......
......@@ -39,12 +39,14 @@ module API
optional :my_reaction_emoji, type: String, desc: 'Return epics reacted by the authenticated user by the given emoji'
use :pagination
end
get ':id/(-/)epics' do
epics = paginate(find_epics(finder_params: { group_id: user_group.id })).with_api_entity_associations
[':id/epics', ':id/-/epics'].each do |path|
get path do
epics = paginate(find_epics(finder_params: { group_id: user_group.id })).with_api_entity_associations
# issuable_metadata has to be set because `Entities::Epic` doesn't inherit from `Entities::IssuableEntity`
extra_options = { issuable_metadata: Gitlab::IssuableMetadata.new(current_user, epics).data, with_labels_details: declared_params[:with_labels_details] }
present epics, epic_options.merge(extra_options)
# issuable_metadata has to be set because `Entities::Epic` doesn't inherit from `Entities::IssuableEntity`
extra_options = { issuable_metadata: Gitlab::IssuableMetadata.new(current_user, epics).data, with_labels_details: declared_params[:with_labels_details] }
present epics, epic_options.merge(extra_options)
end
end
desc 'Get details of an epic' do
......@@ -53,10 +55,12 @@ module API
params do
requires :epic_iid, type: Integer, desc: 'The internal ID of an epic'
end
get ':id/(-/)epics/:epic_iid' do
authorize_can_read!
[':id/epics/:epic_iid', ':id/-/epics/:epic_iid'].each do |path|
get path do
authorize_can_read!
present epic, epic_options.merge(include_subscribed: true)
present epic, epic_options.merge(include_subscribed: true)
end
end
desc 'Create a new epic' do
......
......@@ -4,6 +4,8 @@ module EE
module API
module Entities
class Epic < Grape::Entity
include ::API::Helpers::RelatedResourcesHelpers
can_admin_epic = ->(epic, opts) { Ability.allowed?(opts[:user], :admin_epic, epic) }
expose :id
......@@ -77,6 +79,20 @@ module EE
def web_edit_url
::Gitlab::Routing.url_helpers.group_epic_path(object.group, object)
end
expose :_links do
expose :self do |epic|
expose_url(api_v4_groups_epics_path(id: epic.group_id, epic_iid: epic.iid))
end
expose :epic_issues do |epic|
expose_url(api_v4_groups_epics_issues_path(id: epic.group_id, epic_iid: epic.iid))
end
expose :group do |epic|
expose_url(api_v4_groups_path(id: epic.group_id))
end
end
end
end
end
......
......@@ -51,10 +51,19 @@
"relative": {"type": "string"},
"full": {"type": "string"}
},
"subscribed": { "type": ["boolean", "null"] }
"subscribed": { "type": ["boolean", "null"] },
"_links": {
"type": "object",
"properties": {
"self": { "type": "uri" },
"epic_issues": { "type": "uri" },
"group": { "type": "uri" },
"additionalProperties": false
}
}
},
"required": [
"id", "iid", "group_id", "title", "confidential"
"id", "iid", "group_id", "title", "confidential", "_links"
],
"additionalProperties": false
}
......@@ -522,6 +522,16 @@ RSpec.describe API::Epics do
expect(json_response['references']['full']).to eq("#{epic.group.path}&#{epic.iid}")
end
it 'exposes links' do
get api(url)
links = json_response['_links']
expect(links['self']).to end_with("/api/v4/groups/#{epic.group.id}/epics/#{epic.iid}")
expect(links['epic_issues']).to end_with("/api/v4/groups/#{epic.group.id}/epics/#{epic.iid}/issues")
expect(links['group']).to end_with("/api/v4/groups/#{epic.group.id}")
end
it_behaves_like 'can admin epics'
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