diff options
-rw-r--r-- | doc/api/award_emoji.md | 204 | ||||
-rw-r--r-- | lib/api/api.rb | 1 | ||||
-rw-r--r-- | lib/api/award_emoji.rb | 151 | ||||
-rw-r--r-- | spec/requests/api/award_emoji_spec.rb | 59 |
4 files changed, 341 insertions, 74 deletions
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md new file mode 100644 index 00000000000..ecabc5dc7b8 --- /dev/null +++ b/doc/api/award_emoji.md @@ -0,0 +1,204 @@ +# 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. diff --git a/lib/api/api.rb b/lib/api/api.rb index ef23c4d5de0..0e7a1cc2623 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -26,7 +26,6 @@ module API # Ensure the namespace is right, otherwise we might load Grape::API::Helpers helpers ::API::Helpers - # Sort these alphabetically mount ::API::AwardEmoji mount ::API::Branches mount ::API::Builds diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index a7949b9e11d..985590312e3 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -1,7 +1,6 @@ module API class AwardEmoji < Grape::API before { authenticate! } - AWARDABLES = [Issue, MergeRequest] resource :projects do @@ -9,93 +8,109 @@ module API awardable_string = awardable_type.to_s.underscore.pluralize awardable_id_string = "#{awardable_type.to_s.underscore}_id" - # Get a list of project +awardable+ award emoji - # - # Parameters: - # 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]) + [ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji", + ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji" + ].each do |endpoint| - if can_read_awardable?(awardable) - awards = paginate(awardable.award_emoji) - present awards, with: Entities::AwardEmoji - else - not_found!("Award Emoji") + # Get a list of project +awardable+ award emoji + # + # Parameters: + # 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 endpoint do + if can_read_awardable? + awards = paginate(awardable.award_emoji) + present awards, with: Entities::AwardEmoji + else + not_found!("Award Emoji") + 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) - present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji - else - not_found!("Award Emoji") + # 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 "#{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 - # Award a new Emoji - # - # Parameters: - # id (required) - The ID of a project - # awardable_id (required) - The ID of an issue or mr - # name (required) - The name of a award_emoji (without colons) - # Example Request: - # POST /projects/:id/issues/:awardable_id/notes - post ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji" do - required_attributes! [:name] + # Award a new Emoji + # + # Parameters: + # id (required) - The ID of a project + # awardable_id (required) - The ID of an issue or mr + # name (required) - The name of a award_emoji (without colons) + # Example Request: + # POST /projects/:id/issues/:awardable_id/award_emoji + post endpoint do + 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?(awardable) + not_found!('Award Emoji') unless can_read_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 - present award, with: Entities::AwardEmoji - else - not_found!("Award Emoji #{award.errors.messages}") + if award.save + present award, with: Entities::AwardEmoji + else + not_found!("Award Emoji #{award.errors.messages}") + end end - end - # Delete a +awardables+ award emoji - # - # Parameters: - # id (required) - The ID of a project - # awardable_id (required) - The ID of an issue or MR - # award_emoji_id (required) - The ID of an award emoji - # Example Request: - # DELETE /projects/:id/issues/:noteable_id/notes/:note_id - delete ":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]) - award = awardable.award_emoji.find(params[:award_id]) + # Delete a +awardables+ award emoji + # + # Parameters: + # id (required) - The ID of a project + # awardable_id (required) - The ID of an issue or MR + # award_emoji_id (required) - The ID of an award emoji + # Example Request: + # DELETE /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id + delete "#{endpoint}/:award_id" do + 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 - present award, with: Entities::AwardEmoji + award.destroy + present award, with: Entities::AwardEmoji + 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 can?(current_user, ability, awardable) 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 diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 49964cb51de..7cf2c21dae8 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -5,9 +5,10 @@ describe API::API, api: true do let(:user) { create(:user) } let!(:project) { create(:project) } let(:issue) { create(:issue, project: project, author: user) } - let!(:award_emoji) { create(:award_emoji, awardable: issue, user: user) } - let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, 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!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) } + let!(:note) { create(:note, project: project, noteable: issue) } before { project.team << [user, :master] } @@ -49,6 +50,19 @@ describe API::API, api: true do 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 context 'on an issue' do it "returns the award emoji" do @@ -73,7 +87,7 @@ describe API::API, api: true do expect(response.status).to eq(200) 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") end end @@ -89,6 +103,18 @@ describe API::API, api: true do 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 context "on an issue" do it "creates a new award emoji" do @@ -113,7 +139,18 @@ describe API::API, api: true do 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 it 'deletes the award' do expect do @@ -146,4 +183,16 @@ describe API::API, api: true do 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 |