Commit 2d17d6f1bc4ddfeb001dacbc427fa3dc9af694e4

Authored by Dmitriy Zaporozhets
2 parents aaa1c942 24047e1e

Merge pull request #2123 from NARKOZ/notes-api

Notes API
CHANGELOG
1 1 v 3.2.0
  2 + - [API] create notes for snippets and issues
  3 + - [API] list notes for snippets and issues
  4 + - [API] list project wall notes
2 5 - Remove project code - use path instead
3 6 - added username field to user
4 7 - rake task to fill usernames based on emails create namespaces for users
... ... @@ -10,7 +13,7 @@ v 3.2.0
10 13 - Fixes commit patches getting escaped (see #2036)
11 14 - Support diff and patch generation for commits and merge request
12 15 - MergeReqest doesn't generate a temporary file for the patch any more
13   - - Update the UI to allow downloading Patch or Diff
  16 + - Update the UI to allow downloading Patch or Diff
14 17  
15 18 v 3.1.0
16 19 - Updated gems
... ... @@ -48,7 +51,7 @@ v 3.0.0
48 51 - Fixed bug with gitolite keys
49 52 - UI improved
50 53 - Increased perfomance of application
51   - - Show user avatar in last commit when browsing Files
  54 + - Show user avatar in last commit when browsing Files
52 55 - Refactored Gitlab::Merge
53 56 - Use Font Awsome for icons
54 57 - Separate observing of Note and MergeRequestsa
... ...
app/views/help/api.html.haml
... ... @@ -21,6 +21,8 @@
21 21 = link_to "Issues", "#issues", 'data-toggle' => 'tab'
22 22 %li
23 23 = link_to "Milestones", "#milestones", 'data-toggle' => 'tab'
  24 + %li
  25 + = link_to "Notes", "#notes", 'data-toggle' => 'tab'
24 26  
25 27 .tab-content
26 28 .tab-pane.active#README
... ... @@ -94,3 +96,12 @@
94 96 .file_content.wiki
95 97 = preserve do
96 98 = markdown File.read(Rails.root.join("doc", "api", "milestones.md"))
  99 +
  100 + .tab-pane#notes
  101 + .file_holder
  102 + .file_title
  103 + %i.icon-file
  104 + Notes
  105 + .file_content.wiki
  106 + = preserve do
  107 + = markdown File.read(Rails.root.join("doc", "api", "notes.md"))
... ...
doc/api/notes.md 0 → 100644
... ... @@ -0,0 +1,121 @@
  1 +## List notes
  2 +
  3 +### List project wall notes
  4 +
  5 +Get a list of project wall notes.
  6 +
  7 +```
  8 +GET /projects/:id/notes
  9 +```
  10 +
  11 +```json
  12 +[
  13 + {
  14 + "id": 522,
  15 + "body": "The solution is rather tricky",
  16 + "author": {
  17 + "id": 1,
  18 + "email": "john@example.com",
  19 + "name": "John Smith",
  20 + "blocked": false,
  21 + "created_at": "2012-05-23T08:00:58Z"
  22 + },
  23 + "updated_at":"2012-11-27T19:16:44Z",
  24 + "created_at":"2012-11-27T19:16:44Z"
  25 + }
  26 +]
  27 +```
  28 +
  29 +Parameters:
  30 +
  31 ++ `id` (required) - The ID or code name of a project
  32 +
  33 +### List issue notes
  34 +
  35 +Get a list of issue notes.
  36 +
  37 +```
  38 +GET /projects/:id/issues/:issue_id/notes
  39 +```
  40 +
  41 +Parameters:
  42 +
  43 ++ `id` (required) - The ID or code name of a project
  44 ++ `issue_id` (required) - The ID of an issue
  45 +
  46 +### List snippet notes
  47 +
  48 +Get a list of snippet notes.
  49 +
  50 +```
  51 +GET /projects/:id/snippets/:snippet_id/notes
  52 +```
  53 +
  54 +Parameters:
  55 +
  56 ++ `id` (required) - The ID or code name of a project
  57 ++ `snippet_id` (required) - The ID of a snippet
  58 +
  59 +## Single note
  60 +
  61 +### Single issue note
  62 +
  63 +Get an issue note.
  64 +
  65 +```
  66 +GET /projects/:id/issues/:issue_id/:notes/:note_id
  67 +```
  68 +
  69 +Parameters:
  70 +
  71 ++ `id` (required) - The ID or code name of a project
  72 ++ `issue_id` (required) - The ID of a project issue
  73 ++ `note_id` (required) - The ID of an issue note
  74 +
  75 +### Single snippet note
  76 +
  77 +Get a snippet note.
  78 +
  79 +```
  80 +GET /projects/:id/issues/:snippet_id/:notes/:note_id
  81 +```
  82 +
  83 +Parameters:
  84 +
  85 ++ `id` (required) - The ID or code name of a project
  86 ++ `snippet_id` (required) - The ID of a project snippet
  87 ++ `note_id` (required) - The ID of an snippet note
  88 +
  89 +## New note
  90 +
  91 +### New issue note
  92 +
  93 +Create a new issue note.
  94 +
  95 +```
  96 +POST /projects/:id/issues/:issue_id/notes
  97 +```
  98 +
  99 +Parameters:
  100 +
  101 ++ `id` (required) - The ID or code name of a project
  102 ++ `issue_id` (required) - The ID of an issue
  103 ++ `body` (required) - The content of a note
  104 +
  105 +Will return created note with status `201 Created` on success, or `404 Not found` on fail.
  106 +
  107 +### New snippet note
  108 +
  109 +Create a new snippet note.
  110 +
  111 +```
  112 +POST /projects/:id/snippets/:snippet_id/notes
  113 +```
  114 +
  115 +Parameters:
  116 +
  117 ++ `id` (required) - The ID or code name of a project
  118 ++ `snippet_id` (required) - The ID of an snippet
  119 ++ `body` (required) - The content of a note
  120 +
  121 +Will return created note with status `201 Created` on success, or `404 Not found` on fail.
... ...
lib/api.rb
... ... @@ -19,5 +19,6 @@ module Gitlab
19 19 mount Milestones
20 20 mount Session
21 21 mount MergeRequests
  22 + mount Notes
22 23 end
23 24 end
... ...
lib/api/entities.rb
... ... @@ -70,8 +70,15 @@ module Gitlab
70 70 end
71 71  
72 72 class Note < Grape::Entity
  73 + expose :id
  74 + expose :note, as: :body
73 75 expose :author, using: Entities::UserBasic
  76 + expose :updated_at, :created_at
  77 + end
  78 +
  79 + class MRNote < Grape::Entity
74 80 expose :note
  81 + expose :author, using: Entities::UserBasic
75 82 end
76 83 end
77 84 end
... ...
lib/api/merge_requests.rb
... ... @@ -4,9 +4,9 @@ module Gitlab
4 4 before { authenticate! }
5 5  
6 6 resource :projects do
7   -
  7 +
8 8 # List merge requests
9   - #
  9 + #
10 10 # Parameters:
11 11 # id (required) - The ID or code name of a project
12 12 #
... ... @@ -15,24 +15,24 @@ module Gitlab
15 15 #
16 16 get ":id/merge_requests" do
17 17 authorize! :read_merge_request, user_project
18   -
  18 +
19 19 present paginate(user_project.merge_requests), with: Entities::MergeRequest
20 20 end
21   -
  21 +
22 22 # Show MR
23   - #
  23 + #
24 24 # Parameters:
25 25 # id (required) - The ID or code name of a project
26 26 # merge_request_id (required) - The ID of MR
27   - #
  27 + #
28 28 # Example:
29 29 # GET /projects/:id/merge_request/:merge_request_id
30 30 #
31 31 get ":id/merge_request/:merge_request_id" do
32 32 merge_request = user_project.merge_requests.find(params[:merge_request_id])
33   -
  33 +
34 34 authorize! :read_merge_request, merge_request
35   -
  35 +
36 36 present merge_request, with: Entities::MergeRequest
37 37 end
38 38  
... ... @@ -45,17 +45,17 @@ module Gitlab
45 45 # target_branch (required) - The target branch
46 46 # assignee_id - Assignee user ID
47 47 # title (required) - Title of MR
48   - #
  48 + #
49 49 # Example:
50 50 # POST /projects/:id/merge_requests
51 51 #
52 52 post ":id/merge_requests" do
53 53 authorize! :write_merge_request, user_project
54   -
  54 +
55 55 attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title]
56 56 merge_request = user_project.merge_requests.new(attrs)
57 57 merge_request.author = current_user
58   -
  58 +
59 59 if merge_request.save
60 60 merge_request.reload_code
61 61 present merge_request, with: Entities::MergeRequest
... ... @@ -80,9 +80,9 @@ module Gitlab
80 80 put ":id/merge_request/:merge_request_id" do
81 81 attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed]
82 82 merge_request = user_project.merge_requests.find(params[:merge_request_id])
83   -
  83 +
84 84 authorize! :modify_merge_request, merge_request
85   -
  85 +
86 86 if merge_request.update_attributes attrs
87 87 merge_request.reload_code
88 88 merge_request.mark_as_unchecked
... ... @@ -98,7 +98,7 @@ module Gitlab
98 98 # id (required) - The ID or code name of a project
99 99 # merge_request_id (required) - ID of MR
100 100 # note (required) - Text of comment
101   - # Examples:
  101 + # Examples:
102 102 # POST /projects/:id/merge_request/:merge_request_id/comments
103 103 #
104 104 post ":id/merge_request/:merge_request_id/comments" do
... ... @@ -107,7 +107,7 @@ module Gitlab
107 107 note.author = current_user
108 108  
109 109 if note.save
110   - present note, with: Entities::Note
  110 + present note, with: Entities::MRNote
111 111 else
112 112 not_found!
113 113 end
... ...
lib/api/notes.rb 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +module Gitlab
  2 + # Notes API
  3 + class Notes < Grape::API
  4 + before { authenticate! }
  5 +
  6 + NOTEABLE_TYPES = [Issue, Snippet]
  7 +
  8 + resource :projects do
  9 + # Get a list of project wall notes
  10 + #
  11 + # Parameters:
  12 + # id (required) - The ID or code name of a project
  13 + # Example Request:
  14 + # GET /projects/:id/notes
  15 + get ":id/notes" do
  16 + @notes = user_project.common_notes
  17 + present paginate(@notes), with: Entities::Note
  18 + end
  19 +
  20 + NOTEABLE_TYPES.each do |noteable_type|
  21 + noteables_str = noteable_type.to_s.underscore.pluralize
  22 + noteable_id_str = "#{noteable_type.to_s.underscore}_id"
  23 +
  24 + # Get a list of project +noteable+ notes
  25 + #
  26 + # Parameters:
  27 + # id (required) - The ID or code name of a project
  28 + # noteable_id (required) - The ID of an issue or snippet
  29 + # Example Request:
  30 + # GET /projects/:id/issues/:noteable_id/notes
  31 + # GET /projects/:id/snippets/:noteable_id/notes
  32 + get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
  33 + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
  34 + present paginate(@noteable.notes), with: Entities::Note
  35 + end
  36 +
  37 + # Get a single +noteable+ note
  38 + #
  39 + # Parameters:
  40 + # id (required) - The ID or code name of a project
  41 + # noteable_id (required) - The ID of an issue or snippet
  42 + # note_id (required) - The ID of a note
  43 + # Example Request:
  44 + # GET /projects/:id/issues/:noteable_id/notes/:note_id
  45 + # GET /projects/:id/snippets/:noteable_id/notes/:note_id
  46 + get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
  47 + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
  48 + @note = @noteable.notes.find(params[:note_id])
  49 + present @note, with: Entities::Note
  50 + end
  51 +
  52 + # Create a new +noteable+ note
  53 + #
  54 + # Parameters:
  55 + # id (required) - The ID or code name of a project
  56 + # noteable_id (required) - The ID of an issue or snippet
  57 + # body (required) - The content of a note
  58 + # Example Request:
  59 + # POST /projects/:id/issues/:noteable_id/notes
  60 + # POST /projects/:id/snippets/:noteable_id/notes
  61 + post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
  62 + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
  63 + @note = @noteable.notes.new(note: params[:body])
  64 + @note.author = current_user
  65 + @note.project = user_project
  66 +
  67 + if @note.save
  68 + present @note, with: Entities::Note
  69 + else
  70 + not_found!
  71 + end
  72 + end
  73 + end
  74 + end
  75 + end
  76 +end
... ...
spec/requests/api/notes_spec.rb 0 → 100644
... ... @@ -0,0 +1,90 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::API do
  4 + include ApiHelpers
  5 +
  6 + let(:user) { create(:user) }
  7 + let!(:project) { create(:project, owner: user) }
  8 + let!(:issue) { create(:issue, project: project, author: user) }
  9 + let!(:snippet) { create(:snippet, project: project, author: user) }
  10 + let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
  11 + let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
  12 + let!(:wall_note) { create(:note, project: project, author: user) }
  13 + before { project.add_access(user, :read) }
  14 +
  15 + describe "GET /projects/:id/notes" do
  16 + context "when unauthenticated" do
  17 + it "should return authentication error" do
  18 + get api("/projects/#{project.id}/notes")
  19 + response.status.should == 401
  20 + end
  21 + end
  22 +
  23 + context "when authenticated" do
  24 + it "should return project wall notes" do
  25 + get api("/projects/#{project.id}/notes", user)
  26 + response.status.should == 200
  27 + json_response.should be_an Array
  28 + json_response.first['body'].should == wall_note.note
  29 + end
  30 + end
  31 + end
  32 +
  33 + describe "GET /projects/:id/noteable/:noteable_id/notes" do
  34 + context "when noteable is an Issue" do
  35 + it "should return an array of issue notes" do
  36 + get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
  37 + response.status.should == 200
  38 + json_response.should be_an Array
  39 + json_response.first['body'].should == issue_note.note
  40 + end
  41 + end
  42 +
  43 + context "when noteable is a Snippet" do
  44 + it "should return an array of snippet notes" do
  45 + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
  46 + response.status.should == 200
  47 + json_response.should be_an Array
  48 + json_response.first['body'].should == snippet_note.note
  49 + end
  50 + end
  51 + end
  52 +
  53 + describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do
  54 + context "when noteable is an Issue" do
  55 + it "should return an issue note by id" do
  56 + get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user)
  57 + response.status.should == 200
  58 + json_response['body'].should == issue_note.note
  59 + end
  60 + end
  61 +
  62 + context "when noteable is a Snippet" do
  63 + it "should return a snippet note by id" do
  64 + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user)
  65 + response.status.should == 200
  66 + json_response['body'].should == snippet_note.note
  67 + end
  68 + end
  69 + end
  70 +
  71 + describe "POST /projects/:id/noteable/:noteable_id/notes" do
  72 + context "when noteable is an Issue" do
  73 + it "should create a new issue note" do
  74 + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
  75 + response.status.should == 201
  76 + json_response['body'].should == 'hi!'
  77 + json_response['author']['email'].should == user.email
  78 + end
  79 + end
  80 +
  81 + context "when noteable is a Snippet" do
  82 + it "should create a new snippet note" do
  83 + post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!'
  84 + response.status.should == 201
  85 + json_response['body'].should == 'hi!'
  86 + json_response['author']['email'].should == user.email
  87 + end
  88 + end
  89 + end
  90 +end
... ...