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 | v 3.2.0 | 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 | - Remove project code - use path instead | 5 | - Remove project code - use path instead |
3 | - added username field to user | 6 | - added username field to user |
4 | - rake task to fill usernames based on emails create namespaces for users | 7 | - rake task to fill usernames based on emails create namespaces for users |
@@ -10,7 +13,7 @@ v 3.2.0 | @@ -10,7 +13,7 @@ v 3.2.0 | ||
10 | - Fixes commit patches getting escaped (see #2036) | 13 | - Fixes commit patches getting escaped (see #2036) |
11 | - Support diff and patch generation for commits and merge request | 14 | - Support diff and patch generation for commits and merge request |
12 | - MergeReqest doesn't generate a temporary file for the patch any more | 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 | v 3.1.0 | 18 | v 3.1.0 |
16 | - Updated gems | 19 | - Updated gems |
@@ -48,7 +51,7 @@ v 3.0.0 | @@ -48,7 +51,7 @@ v 3.0.0 | ||
48 | - Fixed bug with gitolite keys | 51 | - Fixed bug with gitolite keys |
49 | - UI improved | 52 | - UI improved |
50 | - Increased perfomance of application | 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 | - Refactored Gitlab::Merge | 55 | - Refactored Gitlab::Merge |
53 | - Use Font Awsome for icons | 56 | - Use Font Awsome for icons |
54 | - Separate observing of Note and MergeRequestsa | 57 | - Separate observing of Note and MergeRequestsa |
app/views/help/api.html.haml
@@ -21,6 +21,8 @@ | @@ -21,6 +21,8 @@ | ||
21 | = link_to "Issues", "#issues", 'data-toggle' => 'tab' | 21 | = link_to "Issues", "#issues", 'data-toggle' => 'tab' |
22 | %li | 22 | %li |
23 | = link_to "Milestones", "#milestones", 'data-toggle' => 'tab' | 23 | = link_to "Milestones", "#milestones", 'data-toggle' => 'tab' |
24 | + %li | ||
25 | + = link_to "Notes", "#notes", 'data-toggle' => 'tab' | ||
24 | 26 | ||
25 | .tab-content | 27 | .tab-content |
26 | .tab-pane.active#README | 28 | .tab-pane.active#README |
@@ -94,3 +96,12 @@ | @@ -94,3 +96,12 @@ | ||
94 | .file_content.wiki | 96 | .file_content.wiki |
95 | = preserve do | 97 | = preserve do |
96 | = markdown File.read(Rails.root.join("doc", "api", "milestones.md")) | 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 @@ | @@ -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,8 +70,15 @@ module Gitlab | ||
70 | end | 70 | end |
71 | 71 | ||
72 | class Note < Grape::Entity | 72 | class Note < Grape::Entity |
73 | + expose :id | ||
74 | + expose :note, as: :body | ||
73 | expose :author, using: Entities::UserBasic | 75 | expose :author, using: Entities::UserBasic |
76 | + expose :updated_at, :created_at | ||
77 | + end | ||
78 | + | ||
79 | + class MRNote < Grape::Entity | ||
74 | expose :note | 80 | expose :note |
81 | + expose :author, using: Entities::UserBasic | ||
75 | end | 82 | end |
76 | end | 83 | end |
77 | end | 84 | end |
lib/api/merge_requests.rb
@@ -4,9 +4,9 @@ module Gitlab | @@ -4,9 +4,9 @@ module Gitlab | ||
4 | before { authenticate! } | 4 | before { authenticate! } |
5 | 5 | ||
6 | resource :projects do | 6 | resource :projects do |
7 | - | 7 | + |
8 | # List merge requests | 8 | # List merge requests |
9 | - # | 9 | + # |
10 | # Parameters: | 10 | # Parameters: |
11 | # id (required) - The ID or code name of a project | 11 | # id (required) - The ID or code name of a project |
12 | # | 12 | # |
@@ -15,24 +15,24 @@ module Gitlab | @@ -15,24 +15,24 @@ module Gitlab | ||
15 | # | 15 | # |
16 | get ":id/merge_requests" do | 16 | get ":id/merge_requests" do |
17 | authorize! :read_merge_request, user_project | 17 | authorize! :read_merge_request, user_project |
18 | - | 18 | + |
19 | present paginate(user_project.merge_requests), with: Entities::MergeRequest | 19 | present paginate(user_project.merge_requests), with: Entities::MergeRequest |
20 | end | 20 | end |
21 | - | 21 | + |
22 | # Show MR | 22 | # Show MR |
23 | - # | 23 | + # |
24 | # Parameters: | 24 | # Parameters: |
25 | # id (required) - The ID or code name of a project | 25 | # id (required) - The ID or code name of a project |
26 | # merge_request_id (required) - The ID of MR | 26 | # merge_request_id (required) - The ID of MR |
27 | - # | 27 | + # |
28 | # Example: | 28 | # Example: |
29 | # GET /projects/:id/merge_request/:merge_request_id | 29 | # GET /projects/:id/merge_request/:merge_request_id |
30 | # | 30 | # |
31 | get ":id/merge_request/:merge_request_id" do | 31 | get ":id/merge_request/:merge_request_id" do |
32 | merge_request = user_project.merge_requests.find(params[:merge_request_id]) | 32 | merge_request = user_project.merge_requests.find(params[:merge_request_id]) |
33 | - | 33 | + |
34 | authorize! :read_merge_request, merge_request | 34 | authorize! :read_merge_request, merge_request |
35 | - | 35 | + |
36 | present merge_request, with: Entities::MergeRequest | 36 | present merge_request, with: Entities::MergeRequest |
37 | end | 37 | end |
38 | 38 | ||
@@ -45,17 +45,17 @@ module Gitlab | @@ -45,17 +45,17 @@ module Gitlab | ||
45 | # target_branch (required) - The target branch | 45 | # target_branch (required) - The target branch |
46 | # assignee_id - Assignee user ID | 46 | # assignee_id - Assignee user ID |
47 | # title (required) - Title of MR | 47 | # title (required) - Title of MR |
48 | - # | 48 | + # |
49 | # Example: | 49 | # Example: |
50 | # POST /projects/:id/merge_requests | 50 | # POST /projects/:id/merge_requests |
51 | # | 51 | # |
52 | post ":id/merge_requests" do | 52 | post ":id/merge_requests" do |
53 | authorize! :write_merge_request, user_project | 53 | authorize! :write_merge_request, user_project |
54 | - | 54 | + |
55 | attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] | 55 | attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] |
56 | merge_request = user_project.merge_requests.new(attrs) | 56 | merge_request = user_project.merge_requests.new(attrs) |
57 | merge_request.author = current_user | 57 | merge_request.author = current_user |
58 | - | 58 | + |
59 | if merge_request.save | 59 | if merge_request.save |
60 | merge_request.reload_code | 60 | merge_request.reload_code |
61 | present merge_request, with: Entities::MergeRequest | 61 | present merge_request, with: Entities::MergeRequest |
@@ -80,9 +80,9 @@ module Gitlab | @@ -80,9 +80,9 @@ module Gitlab | ||
80 | put ":id/merge_request/:merge_request_id" do | 80 | put ":id/merge_request/:merge_request_id" do |
81 | attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed] | 81 | attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed] |
82 | merge_request = user_project.merge_requests.find(params[:merge_request_id]) | 82 | merge_request = user_project.merge_requests.find(params[:merge_request_id]) |
83 | - | 83 | + |
84 | authorize! :modify_merge_request, merge_request | 84 | authorize! :modify_merge_request, merge_request |
85 | - | 85 | + |
86 | if merge_request.update_attributes attrs | 86 | if merge_request.update_attributes attrs |
87 | merge_request.reload_code | 87 | merge_request.reload_code |
88 | merge_request.mark_as_unchecked | 88 | merge_request.mark_as_unchecked |
@@ -98,7 +98,7 @@ module Gitlab | @@ -98,7 +98,7 @@ module Gitlab | ||
98 | # id (required) - The ID or code name of a project | 98 | # id (required) - The ID or code name of a project |
99 | # merge_request_id (required) - ID of MR | 99 | # merge_request_id (required) - ID of MR |
100 | # note (required) - Text of comment | 100 | # note (required) - Text of comment |
101 | - # Examples: | 101 | + # Examples: |
102 | # POST /projects/:id/merge_request/:merge_request_id/comments | 102 | # POST /projects/:id/merge_request/:merge_request_id/comments |
103 | # | 103 | # |
104 | post ":id/merge_request/:merge_request_id/comments" do | 104 | post ":id/merge_request/:merge_request_id/comments" do |
@@ -107,7 +107,7 @@ module Gitlab | @@ -107,7 +107,7 @@ module Gitlab | ||
107 | note.author = current_user | 107 | note.author = current_user |
108 | 108 | ||
109 | if note.save | 109 | if note.save |
110 | - present note, with: Entities::Note | 110 | + present note, with: Entities::MRNote |
111 | else | 111 | else |
112 | not_found! | 112 | not_found! |
113 | end | 113 | end |
@@ -0,0 +1,76 @@ | @@ -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 @@ | @@ -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 |