Commit 2d17d6f1bc4ddfeb001dacbc427fa3dc9af694e4
Exists in
master
and in
4 other branches
Merge pull request #2123 from NARKOZ/notes-api
Notes API
Showing
8 changed files
with
326 additions
and
17 deletions
Show diff stats
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")) | ... | ... |
... | ... | @@ -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
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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |