Commit b683a71aa1230f17f9df47661c77dfeae27027de

Authored by Dmitriy Zaporozhets
2 parents 8b7e404b fbb41100

Merge pull request #1135 from NARKOZ/api

Issues API
app/views/help/api.html.haml
1 1 %h3 API
2 2 .back_link
3   - = link_to help_path do
  3 + = link_to help_path do
4 4 ← to index
5 5 %hr
6 6  
7 7 %ol
8   - %li
  8 + %li
9 9 %a{:href => "#README"} README
10   - %li
  10 + %li
11 11 %a{:href => "#projects"} Projects
12   - %li
  12 + %li
13 13 %a{:href => "#users"} Users
  14 + %li
  15 + %a{:href => "#issues"} Issues
14 16  
15 17 .file_holder#README
16 18 .file_title
... ... @@ -39,3 +41,13 @@
39 41 .file_content.wiki
40 42 = preserve do
41 43 = markdown File.read(Rails.root.join("doc", "api", "users.md"))
  44 +
  45 +%br
  46 +
  47 +.file_holder#issues
  48 + .file_title
  49 + %i.icon-file
  50 + Issues
  51 + .file_content.wiki
  52 + = preserve do
  53 + = markdown File.read(Rails.root.join("doc", "api", "issues.md"))
... ...
doc/api/README.md
... ... @@ -27,3 +27,4 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en
27 27  
28 28 + [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md)
29 29 + [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md)
  30 ++ [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md)
... ...
doc/api/issues.md 0 → 100644
... ... @@ -0,0 +1,181 @@
  1 +## List issues
  2 +
  3 +Get all issues created by authenticed user.
  4 +
  5 +```
  6 +GET /issues
  7 +```
  8 +
  9 +```json
  10 +[
  11 + {
  12 + "id": 43,
  13 + "title": "4xx/5xx pages",
  14 + "description": "",
  15 + "labels": [ ],
  16 + "milestone": null,
  17 + "assignee": null,
  18 + "author": {
  19 + "id": 1,
  20 + "email": "john@example.com",
  21 + "name": "John Smith",
  22 + "blocked": false,
  23 + "created_at": "2012-05-23T08:00:58Z"
  24 + },
  25 + "closed": true,
  26 + "updated_at": "2012-07-02T17:53:12Z",
  27 + "created_at": "2012-07-02T17:53:12Z"
  28 + },
  29 + {
  30 + "id": 42,
  31 + "title": "Add user settings",
  32 + "description": "",
  33 + "labels": [
  34 + "feature"
  35 + ],
  36 + "milestone": {
  37 + "id": 1,
  38 + "title": "v1.0",
  39 + "description": "",
  40 + "due_date": "2012-07-20",
  41 + "closed": false,
  42 + "updated_at": "2012-07-04T13:42:48Z",
  43 + "created_at": "2012-07-04T13:42:48Z"
  44 + },
  45 + "assignee": {
  46 + "id": 2,
  47 + "email": "jack@example.com",
  48 + "name": "Jack Smith",
  49 + "blocked": false,
  50 + "created_at": "2012-05-23T08:01:01Z"
  51 + },
  52 + "author": {
  53 + "id": 1,
  54 + "email": "john@example.com",
  55 + "name": "John Smith",
  56 + "blocked": false,
  57 + "created_at": "2012-05-23T08:00:58Z"
  58 + },
  59 + "closed": false,
  60 + "updated_at": "2012-07-12T13:43:19Z",
  61 + "created_at": "2012-06-28T12:58:06Z"
  62 + }
  63 +]
  64 +```
  65 +
  66 +## List project issues
  67 +
  68 +Get a list of project issues.
  69 +
  70 +```
  71 +GET /projects/:id/issues
  72 +```
  73 +
  74 +Parameters:
  75 +
  76 ++ `id` (required) - The code name of a project
  77 +
  78 +## Single issue
  79 +
  80 +Get a project issue.
  81 +
  82 +```
  83 +GET /projects/:id/issues/:issue_id
  84 +```
  85 +
  86 +Parameters:
  87 +
  88 ++ `id` (required) - The code name of a project
  89 ++ `issue_id` (required) - The ID of a project issue
  90 +
  91 +```json
  92 +{
  93 + "id": 42,
  94 + "title": "Add user settings",
  95 + "description": "",
  96 + "labels": [
  97 + "feature"
  98 + ],
  99 + "milestone": {
  100 + "id": 1,
  101 + "title": "v1.0",
  102 + "description": "",
  103 + "due_date": "2012-07-20",
  104 + "closed": false,
  105 + "updated_at": "2012-07-04T13:42:48Z",
  106 + "created_at": "2012-07-04T13:42:48Z"
  107 + },
  108 + "assignee": {
  109 + "id": 2,
  110 + "email": "jack@example.com",
  111 + "name": "Jack Smith",
  112 + "blocked": false,
  113 + "created_at": "2012-05-23T08:01:01Z"
  114 + },
  115 + "author": {
  116 + "id": 1,
  117 + "email": "john@example.com",
  118 + "name": "John Smith",
  119 + "blocked": false,
  120 + "created_at": "2012-05-23T08:00:58Z"
  121 + },
  122 + "closed": false,
  123 + "updated_at": "2012-07-12T13:43:19Z",
  124 + "created_at": "2012-06-28T12:58:06Z"
  125 +}
  126 +```
  127 +
  128 +## New issue
  129 +
  130 +Create a new project issue.
  131 +
  132 +```
  133 +POST /projects/:id/issues
  134 +```
  135 +
  136 +Parameters:
  137 +
  138 ++ `id` (required) - The code name of a project
  139 ++ `title` (required) - The title of an issue
  140 ++ `description` (optional) - The description of an issue
  141 ++ `assignee_id` (optional) - The ID of a user to assign issue
  142 ++ `milestone_id` (optional) - The ID of a milestone to assign issue
  143 ++ `labels` (optional) - Comma-separated label names for an issue
  144 +
  145 +Will return created issue with status `201 Created` on success, or `404 Not found` on fail.
  146 +
  147 +## Edit issue
  148 +
  149 +Update an existing project issue.
  150 +
  151 +```
  152 +PUT /projects/:id/issues/:issue_id
  153 +```
  154 +
  155 +Parameters:
  156 +
  157 ++ `id` (required) - The code name of a project
  158 ++ `issue_id` (required) - The ID of a project's issue
  159 ++ `title` (optional) - The title of an issue
  160 ++ `description` (optional) - The description of an issue
  161 ++ `assignee_id` (optional) - The ID of a user to assign issue
  162 ++ `milestone_id` (optional) - The ID of a milestone to assign issue
  163 ++ `labels` (optional) - Comma-separated label names for an issue
  164 ++ `closed` (optional) - The state of an issue (0 = false, 1 = true)
  165 +
  166 +Will return updated issue with status `200 OK` on success, or `404 Not found` on fail.
  167 +
  168 +## Delete issue
  169 +
  170 +Delete existing project issue.
  171 +
  172 +```
  173 +DELETE /projects/:id/issues/:issue_id
  174 +```
  175 +
  176 +Parameters:
  177 +
  178 ++ `id` (required) - The code name of a project
  179 ++ `issue_id` (required) - The ID of a project's issue
  180 +
  181 +Status code `200` will be returned on success.
... ...
lib/api.rb
... ... @@ -15,5 +15,6 @@ module Gitlab
15 15  
16 16 mount Users
17 17 mount Projects
  18 + mount Issues
18 19 end
19 20 end
... ...
lib/api/entities.rb
... ... @@ -16,11 +16,7 @@ module Gitlab
16 16 expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
17 17 end
18 18  
19   - class ProjectRepositoryBranches < Grape::Entity
20   - expose :name, :commit
21   - end
22   -
23   - class ProjectRepositoryTags < Grape::Entity
  19 + class RepoObject < Grape::Entity
24 20 expose :name, :commit
25 21 end
26 22  
... ... @@ -29,5 +25,17 @@ module Gitlab
29 25 expose :author, :using => Entities::UserBasic
30 26 expose :expires_at, :updated_at, :created_at
31 27 end
  28 +
  29 + class Milestone < Grape::Entity
  30 + expose :id, :title, :description, :due_date, :closed, :updated_at, :created_at
  31 + end
  32 +
  33 + class Issue < Grape::Entity
  34 + expose :id, :title, :description
  35 + expose :label_list, :as => :labels
  36 + expose :milestone, :using => Entities::Milestone
  37 + expose :assignee, :author, :using => Entities::UserBasic
  38 + expose :closed, :updated_at, :created_at
  39 + end
32 40 end
33 41 end
... ...
lib/api/helpers.rb
... ... @@ -4,6 +4,10 @@ module Gitlab
4 4 @current_user ||= User.find_by_authentication_token(params[:private_token])
5 5 end
6 6  
  7 + def user_project
  8 + @project ||= current_user.projects.find_by_code(params[:id])
  9 + end
  10 +
7 11 def authenticate!
8 12 error!({'message' => '401 Unauthorized'}, 401) unless current_user
9 13 end
... ...
lib/api/issues.rb 0 → 100644
... ... @@ -0,0 +1,111 @@
  1 +module Gitlab
  2 + # Issues API
  3 + class Issues < Grape::API
  4 + before { authenticate! }
  5 +
  6 + resource :issues do
  7 + # Get currently authenticated user's issues
  8 + #
  9 + # Example Request:
  10 + # GET /issues
  11 + get do
  12 + present current_user.issues, :with => Entities::Issue
  13 + end
  14 + end
  15 +
  16 + resource :projects do
  17 + # Get a list of project issues
  18 + #
  19 + # Parameters:
  20 + # id (required) - The code name of a project
  21 + # Example Request:
  22 + # GET /projects/:id/issues
  23 + get ":id/issues" do
  24 + present user_project.issues, :with => Entities::Issue
  25 + end
  26 +
  27 + # Get a single project issue
  28 + #
  29 + # Parameters:
  30 + # id (required) - The code name of a project
  31 + # issue_id (required) - The ID of a project issue
  32 + # Example Request:
  33 + # GET /projects/:id/issues/:issue_id
  34 + get ":id/issues/:issue_id" do
  35 + @issue = user_project.issues.find(params[:issue_id])
  36 + present @issue, :with => Entities::Issue
  37 + end
  38 +
  39 + # Create a new project issue
  40 + #
  41 + # Parameters:
  42 + # id (required) - The code name of a project
  43 + # title (required) - The title of an issue
  44 + # description (optional) - The description of an issue
  45 + # assignee_id (optional) - The ID of a user to assign issue
  46 + # milestone_id (optional) - The ID of a milestone to assign issue
  47 + # labels (optional) - The labels of an issue
  48 + # Example Request:
  49 + # POST /projects/:id/issues
  50 + post ":id/issues" do
  51 + @issue = user_project.issues.new(
  52 + :title => params[:title],
  53 + :description => params[:description],
  54 + :assignee_id => params[:assignee_id],
  55 + :milestone_id => params[:milestone_id],
  56 + :label_list => params[:labels]
  57 + )
  58 + @issue.author = current_user
  59 +
  60 + if @issue.save
  61 + present @issue, :with => Entities::Issue
  62 + else
  63 + error!({'message' => '404 Not found'}, 404)
  64 + end
  65 + end
  66 +
  67 + # Update an existing issue
  68 + #
  69 + # Parameters:
  70 + # id (required) - The code name of a project
  71 + # issue_id (required) - The ID of a project issue
  72 + # title (optional) - The title of an issue
  73 + # description (optional) - The description of an issue
  74 + # assignee_id (optional) - The ID of a user to assign issue
  75 + # milestone_id (optional) - The ID of a milestone to assign issue
  76 + # labels (optional) - The labels of an issue
  77 + # closed (optional) - The state of an issue (0 = false, 1 = true)
  78 + # Example Request:
  79 + # PUT /projects/:id/issues/:issue_id
  80 + put ":id/issues/:issue_id" do
  81 + @issue = user_project.issues.find(params[:issue_id])
  82 + parameters = {
  83 + :title => (params[:title] || @issue.title),
  84 + :description => (params[:description] || @issue.description),
  85 + :assignee_id => (params[:assignee_id] || @issue.assignee_id),
  86 + :milestone_id => (params[:milestone_id] || @issue.milestone_id),
  87 + :label_list => (params[:labels] || @issue.label_list),
  88 + :closed => (params[:closed] || @issue.closed)
  89 + }
  90 +
  91 + if @issue.update_attributes(parameters)
  92 + present @issue, :with => Entities::Issue
  93 + else
  94 + error!({'message' => '404 Not found'}, 404)
  95 + end
  96 + end
  97 +
  98 + # Delete a project issue
  99 + #
  100 + # Parameters:
  101 + # id (required) - The code name of a project
  102 + # issue_id (required) - The ID of a project issue
  103 + # Example Request:
  104 + # DELETE /projects/:id/issues/:issue_id
  105 + delete ":id/issues/:issue_id" do
  106 + @issue = user_project.issues.find(params[:issue_id])
  107 + @issue.destroy
  108 + end
  109 + end
  110 + end
  111 +end
... ...
lib/api/projects.rb
... ... @@ -20,8 +20,7 @@ module Gitlab
20 20 # Example Request:
21 21 # GET /projects/:id
22 22 get ":id" do
23   - @project = current_user.projects.find_by_code(params[:id])
24   - present @project, :with => Entities::Project
  23 + present user_project, :with => Entities::Project
25 24 end
26 25  
27 26 # Get a project repository branches
... ... @@ -31,8 +30,7 @@ module Gitlab
31 30 # Example Request:
32 31 # GET /projects/:id/repository/branches
33 32 get ":id/repository/branches" do
34   - @project = current_user.projects.find_by_code(params[:id])
35   - present @project.repo.heads.sort_by(&:name), :with => Entities::ProjectRepositoryBranches
  33 + present user_project.repo.heads.sort_by(&:name), :with => Entities::RepoObject
36 34 end
37 35  
38 36 # Get a project repository tags
... ... @@ -42,8 +40,7 @@ module Gitlab
42 40 # Example Request:
43 41 # GET /projects/:id/repository/tags
44 42 get ":id/repository/tags" do
45   - @project = current_user.projects.find_by_code(params[:id])
46   - present @project.repo.tags.sort_by(&:name).reverse, :with => Entities::ProjectRepositoryTags
  43 + present user_project.repo.tags.sort_by(&:name).reverse, :with => Entities::RepoObject
47 44 end
48 45  
49 46 # Get a project snippet
... ... @@ -54,8 +51,7 @@ module Gitlab
54 51 # Example Request:
55 52 # GET /projects/:id/snippets/:snippet_id
56 53 get ":id/snippets/:snippet_id" do
57   - @project = current_user.projects.find_by_code(params[:id])
58   - @snippet = @project.snippets.find(params[:snippet_id])
  54 + @snippet = user_project.snippets.find(params[:snippet_id])
59 55 present @snippet, :with => Entities::ProjectSnippet
60 56 end
61 57  
... ... @@ -70,8 +66,7 @@ module Gitlab
70 66 # Example Request:
71 67 # POST /projects/:id/snippets
72 68 post ":id/snippets" do
73   - @project = current_user.projects.find_by_code(params[:id])
74   - @snippet = @project.snippets.new(
  69 + @snippet = user_project.snippets.new(
75 70 :title => params[:title],
76 71 :file_name => params[:file_name],
77 72 :expires_at => params[:lifetime],
... ... @@ -98,8 +93,7 @@ module Gitlab
98 93 # Example Request:
99 94 # PUT /projects/:id/snippets/:snippet_id
100 95 put ":id/snippets/:snippet_id" do
101   - @project = current_user.projects.find_by_code(params[:id])
102   - @snippet = @project.snippets.find(params[:snippet_id])
  96 + @snippet = user_project.snippets.find(params[:snippet_id])
103 97 parameters = {
104 98 :title => (params[:title] || @snippet.title),
105 99 :file_name => (params[:file_name] || @snippet.file_name),
... ... @@ -122,8 +116,7 @@ module Gitlab
122 116 # Example Request:
123 117 # DELETE /projects/:id/snippets/:snippet_id
124 118 delete ":id/snippets/:snippet_id" do
125   - @project = current_user.projects.find_by_code(params[:id])
126   - @snippet = @project.snippets.find(params[:snippet_id])
  119 + @snippet = user_project.snippets.find(params[:snippet_id])
127 120 @snippet.destroy
128 121 end
129 122  
... ... @@ -135,8 +128,7 @@ module Gitlab
135 128 # Example Request:
136 129 # GET /projects/:id/snippets/:snippet_id/raw
137 130 get ":id/snippets/:snippet_id/raw" do
138   - @project = current_user.projects.find_by_code(params[:id])
139   - @snippet = @project.snippets.find(params[:snippet_id])
  131 + @snippet = user_project.snippets.find(params[:snippet_id])
140 132 present @snippet.content
141 133 end
142 134 end
... ...
spec/api/issues_spec.rb 0 → 100644
... ... @@ -0,0 +1,71 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::API do
  4 + let(:user) { Factory :user }
  5 + let!(:project) { Factory :project, :owner => user }
  6 + let!(:issue) { Factory :issue, :author => user, :assignee => user, :project => project }
  7 + before { project.add_access(user, :read) }
  8 +
  9 + describe "GET /issues" do
  10 + it "should return authentication error" do
  11 + get "#{api_prefix}/issues"
  12 + response.status.should == 401
  13 + end
  14 +
  15 + describe "authenticated GET /issues" do
  16 + it "should return an array of issues" do
  17 + get "#{api_prefix}/issues?private_token=#{user.private_token}"
  18 + response.status.should == 200
  19 + json_response.should be_an Array
  20 + json_response.first['title'].should == issue.title
  21 + end
  22 + end
  23 + end
  24 +
  25 + describe "GET /projects/:id/issues" do
  26 + it "should return project issues" do
  27 + get "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}"
  28 + response.status.should == 200
  29 + json_response.should be_an Array
  30 + json_response.first['title'].should == issue.title
  31 + end
  32 + end
  33 +
  34 + describe "GET /projects/:id/issues/:issue_id" do
  35 + it "should return a project issue by id" do
  36 + get "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}"
  37 + response.status.should == 200
  38 + json_response['title'].should == issue.title
  39 + end
  40 + end
  41 +
  42 + describe "POST /projects/:id/issues" do
  43 + it "should create a new project issue" do
  44 + post "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}",
  45 + :title => 'new issue', :labels => 'label, label2'
  46 + response.status.should == 201
  47 + json_response['title'].should == 'new issue'
  48 + json_response['description'].should be_nil
  49 + json_response['labels'].should == ['label', 'label2']
  50 + end
  51 + end
  52 +
  53 + describe "PUT /projects/:id/issues/:issue_id" do
  54 + it "should update a project issue" do
  55 + put "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}",
  56 + :title => 'updated title', :labels => 'label2', :closed => 1
  57 + response.status.should == 200
  58 + json_response['title'].should == 'updated title'
  59 + json_response['labels'].should == ['label2']
  60 + json_response['closed'].should be_true
  61 + end
  62 + end
  63 +
  64 + describe "DELETE /projects/:id/issues/:issue_id" do
  65 + it "should delete a project issue" do
  66 + expect {
  67 + delete "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}"
  68 + }.to change { Issue.count }.by(-1)
  69 + end
  70 + end
  71 +end
... ...