Commit 05a4a586 authored by Z.J. van de Weg's avatar Z.J. van de Weg

Add endpoints for award emoji on notes

Docs also added.
parent 34558315
# Award Emoji
An awarded emoji tells a thousand words, and can be awarded on issues, merge
requests and notes/comments. Issues, merge requests and notes are further called
`awardables`.
## Issues and MergeRequests
### List an awardables emoji awards
Gets a list of all award emoji
```
GET /projects/:id/issues/:issue_id/award_emoji
GET /projects/:id/merge_requests/:merge_request_id/award_emoji
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `awardable_id` | integer | yes | The ID of an awardable |
```bash
curl -X GET -H "PRIVATE-TOKEN: ir6jesYP_Am5DPy7d1y7" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji
```
Example Response:
```json
[
{
"id": 4,
"name": "1234",
"user": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://localhost:3000/u/root"
},
"created_at": "2016-06-15T10:09:34.206Z",
"updated_at": "2016-06-15T10:09:34.206Z",
"awardable_id": 80,
"awardable_type": "Issue"
},
{
"id": 1,
"name": "microphone",
"user": {
"name": "User 4",
"username": "user4",
"id": 26,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon",
"web_url": "http://localhost:3000/u/user4"
},
"created_at": "2016-06-15T10:09:34.177Z",
"updated_at": "2016-06-15T10:09:34.177Z",
"awardable_id": 80,
"awardable_type": "Issue"
}
]
```
### Get single issue note
Gets a single award emoji
```
GET /projects/:id/issues/:issue_id/award_emoji/:award_id
GET /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `awardable_id` | integer | yes | The ID of an awardable |
| `award_id` | integer | yes | The ID of the award emoji |
```bash
curl -X GET -H "PRIVATE-TOKEN: ir6jesYP_Am5DPy7d1y7" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/1
```
Example Response:
```json
{
"id": 1,
"name": "microphone",
"user": {
"name": "User 4",
"username": "user4",
"id": 26,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon",
"web_url": "http://localhost:3000/u/user4"
},
"created_at": "2016-06-15T10:09:34.177Z",
"updated_at": "2016-06-15T10:09:34.177Z",
"awardable_id": 80,
"awardable_type": "Issue"
}
```
### Award a new emoji
This end point creates an award emoji on the specified resource
```
POST /projects/:id/issues/:issue_id/award_emoji
POST /projects/:id/merge_requests/:merge_request_id/award_emoji
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `awardable_id` | integer | yes | The ID of an awardable |
| `name` | string | yes | The name of the emoji, without colons |
```bash
curl -X POST -H "PRIVATE-TOKEN: ir6jesYP_Am5DPy7d1y7" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji?name=blowfish
```
Example Response:
```json
{
"id": 344,
"name": "blowfish",
"user": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://localhost:3000/u/root"
},
"created_at": "2016-06-17T17:47:29.266Z",
"updated_at": "2016-06-17T17:47:29.266Z",
"awardable_id": 80,
"awardable_type": "Issue"
}
```
### Delete an award emoji
Sometimes its just not meant to be, and you'll have to remove your award. Only available to
admins or the author of the award. Status code 200 on success, 401 if unauthorized.
```
DELETE /projects/:id/issues/:issue_id/award_emoji/:award_id
DELETE /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of an issue |
| `award_id` | integer | yes | The ID of a award_emoji |
```bash
curl -X DELETE -H "PRIVATE-TOKEN: ir6jesYP_Am5DPy7d1y7" http://localhost:3000/api/v3/projects/1/issues/80/award_emoji/344
```
Example Response:
```json
{
"id": 344,
"name": "blowfish",
"user": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://localhost:3000/u/root"
},
"created_at": "2016-06-17T17:47:29.266Z",
"updated_at": "2016-06-17T17:47:29.266Z",
"awardable_id": 80,
"awardable_type": "Issue"
}
```
## Award Emoji on Notes
The end points mentioned above are available for notes too. Notes are a sub
resource of both Issues and MergeRequests. The endpoints changes, for example,
to receive all awards on an issue the endpoint would be:
`/projects/:id/issues/:issue_id/award_emoji`, for the note it would be:
`/projects/:id/issues/:issue_id/notes/:note_id/award_emoji`. Thus after the
resource you'd add `notes/:note_id`.
Parameters stay the same, only the note id has to be added in the URI.
...@@ -26,7 +26,6 @@ module API ...@@ -26,7 +26,6 @@ module API
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers # Ensure the namespace is right, otherwise we might load Grape::API::Helpers
helpers ::API::Helpers helpers ::API::Helpers
# Sort these alphabetically
mount ::API::AwardEmoji mount ::API::AwardEmoji
mount ::API::Branches mount ::API::Branches
mount ::API::Builds mount ::API::Builds
......
module API module API
class AwardEmoji < Grape::API class AwardEmoji < Grape::API
before { authenticate! } before { authenticate! }
AWARDABLES = [Issue, MergeRequest] AWARDABLES = [Issue, MergeRequest]
resource :projects do resource :projects do
...@@ -9,93 +8,109 @@ module API ...@@ -9,93 +8,109 @@ module API
awardable_string = awardable_type.to_s.underscore.pluralize awardable_string = awardable_type.to_s.underscore.pluralize
awardable_id_string = "#{awardable_type.to_s.underscore}_id" awardable_id_string = "#{awardable_type.to_s.underscore}_id"
# Get a list of project +awardable+ award emoji [ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
# ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
# Parameters: ].each do |endpoint|
# id (required) - The ID of a project
# awardable_id (required) - The ID of an issue or MR
# Example Request:
# GET /projects/:id/issues/:awardable_id/award_emoji
get ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji" do
awardable = user_project.send(awardable_string.to_sym).find(params[awardable_id_string])
if can_read_awardable?(awardable) # Get a list of project +awardable+ award emoji
awards = paginate(awardable.award_emoji) #
present awards, with: Entities::AwardEmoji # Parameters:
else # id (required) - The ID of a project
not_found!("Award Emoji") # awardable_id (required) - The ID of an issue or MR
# Example Request:
# GET /projects/:id/issues/:awardable_id/award_emoji
get endpoint do
if can_read_awardable?
awards = paginate(awardable.award_emoji)
present awards, with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
end end
end
# Get a specific award emoji
#
# Parameters:
# id (required) - The ID of a project
# awardable_id (required) - The ID of an issue or MR
# award_id (required) - The ID of the award
# Example Request:
# GET /projects/:id/issues/:awardable_id/award_emoji/:award_id
get ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji/:award_id" do
awardable = user_project.send(awardable_string.to_sym).find(params[awardable_id_string.to_sym])
if can_read_awardable?(awardable) # Get a specific award emoji
present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji #
else # Parameters:
not_found!("Award Emoji") # id (required) - The ID of a project
# awardable_id (required) - The ID of an issue or MR
# award_id (required) - The ID of the award
# Example Request:
# GET /projects/:id/issues/:awardable_id/award_emoji/:award_id
get "#{endpoint}/:award_id" do
if can_read_awardable?
present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
end end
end
# Award a new Emoji # Award a new Emoji
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# awardable_id (required) - The ID of an issue or mr # awardable_id (required) - The ID of an issue or mr
# name (required) - The name of a award_emoji (without colons) # name (required) - The name of a award_emoji (without colons)
# Example Request: # Example Request:
# POST /projects/:id/issues/:awardable_id/notes # POST /projects/:id/issues/:awardable_id/award_emoji
post ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji" do post endpoint do
required_attributes! [:name] required_attributes! [:name]
awardable = user_project.send(awardable_string.to_sym).find(params[awardable_id_string.to_sym]) not_found!('Award Emoji') unless can_read_awardable?
not_found!('Award Emoji') unless can_read_awardable?(awardable)
award = awardable.award_emoji.new(name: params[:name], user: current_user) award = awardable.award_emoji.new(name: params[:name], user: current_user)
if award.save if award.save
present award, with: Entities::AwardEmoji present award, with: Entities::AwardEmoji
else else
not_found!("Award Emoji #{award.errors.messages}") not_found!("Award Emoji #{award.errors.messages}")
end
end end
end
# Delete a +awardables+ award emoji # Delete a +awardables+ award emoji
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# awardable_id (required) - The ID of an issue or MR # awardable_id (required) - The ID of an issue or MR
# award_emoji_id (required) - The ID of an award emoji # award_emoji_id (required) - The ID of an award emoji
# Example Request: # Example Request:
# DELETE /projects/:id/issues/:noteable_id/notes/:note_id # DELETE /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id
delete ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji/:award_id" do delete "#{endpoint}/:award_id" do
awardable = user_project.send(awardable_string.to_sym).find(params[awardable_id_string.to_sym]) award = awardable.award_emoji.find(params[:award_id])
award = awardable.award_emoji.find(params[:award_id])
unauthorized! unless award.user == current_user || current_user.admin? unauthorized! unless award.user == current_user || current_user.admin?
award.destroy award.destroy
present award, with: Entities::AwardEmoji present award, with: Entities::AwardEmoji
end
end end
end end
end end
helpers do
def awardable_read_ability_name(awardable)
end
def can_read_awardable?(awardable) helpers do
def can_read_awardable?
ability = "read_#{awardable.class.to_s.underscore}".to_sym ability = "read_#{awardable.class.to_s.underscore}".to_sym
can?(current_user, ability, awardable) can?(current_user, ability, awardable)
end end
def awardable
@awardable ||=
begin
if params.include?(:note_id)
noteable.notes.find(params[:note_id])
else
noteable
end
end
end
def noteable
if params.include?(:issue_id)
user_project.issues.find(params[:issue_id])
else
user_project.merge_requests.find(params[:merge_request_id])
end
end
end end
end end
end end
...@@ -5,9 +5,10 @@ describe API::API, api: true do ...@@ -5,9 +5,10 @@ describe API::API, api: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project) } let!(:project) { create(:project) }
let(:issue) { create(:issue, project: project, author: user) } let(:issue) { create(:issue, project: project, author: user) }
let!(:award_emoji) { create(:award_emoji, awardable: issue, user: user) } let!(:award_emoji) { create(:award_emoji, awardable: issue, user: user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) } let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) }
let!(:note) { create(:note, project: project, noteable: issue) }
before { project.team << [user, :master] } before { project.team << [user, :master] }
...@@ -49,6 +50,19 @@ describe API::API, api: true do ...@@ -49,6 +50,19 @@ describe API::API, api: true do
end end
end end
describe 'GET /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji' do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
it 'returns an array of award emoji' do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(rocket.name)
end
end
describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do
context 'on an issue' do context 'on an issue' do
it "returns the award emoji" do it "returns the award emoji" do
...@@ -73,7 +87,7 @@ describe API::API, api: true do ...@@ -73,7 +87,7 @@ describe API::API, api: true do
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['name']).to eq(downvote.name) expect(json_response['name']).to eq(downvote.name)
expect(json_response['awardable_id']).to eq(issue.id) expect(json_response['awardable_id']).to eq(merge_request.id)
expect(json_response['awardable_type']).to eq("MergeRequest") expect(json_response['awardable_type']).to eq("MergeRequest")
end end
end end
...@@ -89,6 +103,18 @@ describe API::API, api: true do ...@@ -89,6 +103,18 @@ describe API::API, api: true do
end end
end end
describe 'GET /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji/:award_id' do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
it 'returns an award emoji' do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
expect(response.status).to eq(200)
expect(json_response).not_to be_an Array
expect(json_response['name']).to eq(rocket.name)
end
end
describe "POST /projects/:id/awardable/:awardable_id/award_emoji" do describe "POST /projects/:id/awardable/:awardable_id/award_emoji" do
context "on an issue" do context "on an issue" do
it "creates a new award emoji" do it "creates a new award emoji" do
...@@ -113,7 +139,18 @@ describe API::API, api: true do ...@@ -113,7 +139,18 @@ describe API::API, api: true do
end end
end end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_emoji_id' do describe "POST /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji" do
it 'creates a new award emoji' do
expect do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
end.to change { note.award_emoji.count }.from(0).to(1)
expect(response.status).to eq(201)
expect(json_response['user']['username']).to eq(user.username)
end
end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do
context 'when the awardable is an Issue' do context 'when the awardable is an Issue' do
it 'deletes the award' do it 'deletes the award' do
expect do expect do
...@@ -146,4 +183,16 @@ describe API::API, api: true do ...@@ -146,4 +183,16 @@ describe API::API, api: true do
end end
end end
end end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_emoji_id' do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket', user: user) }
it 'deletes the award' do
expect do
delete api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
end.to change { note.award_emoji.count }.from(1).to(0)
expect(response.status).to eq(200)
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