##// END OF EJS Templates
Adds REST API for TimeEntries (#6823)....
Jean-Philippe Lang -
r4347:f7cf8aa87845
parent child
Show More
@@ -0,0 +1,16
1 api.array :time_entries do
2 @entries.each do |time_entry|
3 api.time_entry do
4 api.id time_entry.id
5 api.project(:id => time_entry.project_id, :name => time_entry.project.name) unless time_entry.project.nil?
6 api.issue(:id => time_entry.issue_id) unless time_entry.issue.nil?
7 api.user(:id => time_entry.user_id, :name => time_entry.user.name) unless time_entry.user.nil?
8 api.activity(:id => time_entry.activity_id, :name => time_entry.activity.name) unless time_entry.activity.nil?
9 api.hours time_entry.hours
10 api.comments time_entry.comments
11 api.spent_on time_entry.spent_on
12 api.created_on time_entry.created_on
13 api.updated_on time_entry.updated_on
14 end
15 end
16 end
@@ -0,0 +1,12
1 api.time_entry do
2 api.id @time_entry.id
3 api.project(:id => @time_entry.project_id, :name => @time_entry.project.name) unless @time_entry.project.nil?
4 api.issue(:id => @time_entry.issue_id) unless @time_entry.issue.nil?
5 api.user(:id => @time_entry.user_id, :name => @time_entry.user.name) unless @time_entry.user.nil?
6 api.activity(:id => @time_entry.activity_id, :name => @time_entry.activity.name) unless @time_entry.activity.nil?
7 api.hours @time_entry.hours
8 api.comments @time_entry.comments
9 api.spent_on @time_entry.spent_on
10 api.created_on @time_entry.created_on
11 api.updated_on @time_entry.updated_on
12 end
@@ -0,0 +1,134
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require "#{File.dirname(__FILE__)}/../../test_helper"
19
20 class ApiTest::TimeEntriesTest < ActionController::IntegrationTest
21 fixtures :all
22
23 def setup
24 Setting.rest_api_enabled = '1'
25 end
26
27 context "GET /time_entries.xml" do
28 should "return time entries" do
29 get '/time_entries.xml', {}, :authorization => credentials('jsmith')
30 assert_response :success
31 assert_equal 'application/xml', @response.content_type
32 assert_tag :tag => 'time_entries',
33 :child => {:tag => 'time_entry', :child => {:tag => 'id', :content => '2'}}
34 end
35 end
36
37 context "GET /time_entries/2.xml" do
38 should "return requested time entry" do
39 get '/time_entries/2.xml', {}, :authorization => credentials('jsmith')
40 assert_response :success
41 assert_equal 'application/xml', @response.content_type
42 assert_tag :tag => 'time_entry',
43 :child => {:tag => 'id', :content => '2'}
44 end
45 end
46
47 context "POST /time_entries.xml" do
48 context "with issue_id" do
49 should "return create time entry" do
50 assert_difference 'TimeEntry.count' do
51 post '/time_entries.xml', {:time_entry => {:issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, :authorization => credentials('jsmith')
52 end
53 assert_response :created
54 assert_equal 'application/xml', @response.content_type
55
56 entry = TimeEntry.first(:order => 'id DESC')
57 assert_equal 'jsmith', entry.user.login
58 assert_equal Issue.find(1), entry.issue
59 assert_equal Project.find(1), entry.project
60 assert_equal Date.parse('2010-12-02'), entry.spent_on
61 assert_equal 3.5, entry.hours
62 assert_equal TimeEntryActivity.find(11), entry.activity
63 end
64 end
65
66 context "with project_id" do
67 should "return create time entry" do
68 assert_difference 'TimeEntry.count' do
69 post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, :authorization => credentials('jsmith')
70 end
71 assert_response :created
72 assert_equal 'application/xml', @response.content_type
73
74 entry = TimeEntry.first(:order => 'id DESC')
75 assert_equal 'jsmith', entry.user.login
76 assert_nil entry.issue
77 assert_equal Project.find(1), entry.project
78 assert_equal Date.parse('2010-12-02'), entry.spent_on
79 assert_equal 3.5, entry.hours
80 assert_equal TimeEntryActivity.find(11), entry.activity
81 end
82 end
83
84 context "with invalid parameters" do
85 should "return errors" do
86 assert_no_difference 'TimeEntry.count' do
87 post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, :authorization => credentials('jsmith')
88 end
89 assert_response :unprocessable_entity
90 assert_equal 'application/xml', @response.content_type
91
92 assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"}
93 end
94 end
95 end
96
97 context "PUT /time_entries/2.xml" do
98 context "with valid parameters" do
99 should "update time entry" do
100 assert_no_difference 'TimeEntry.count' do
101 put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, :authorization => credentials('jsmith')
102 end
103 assert_response :ok
104 assert_equal 'API Update', TimeEntry.find(2).comments
105 end
106 end
107
108 context "with invalid parameters" do
109 should "return errors" do
110 assert_no_difference 'TimeEntry.count' do
111 put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, :authorization => credentials('jsmith')
112 end
113 assert_response :unprocessable_entity
114 assert_equal 'application/xml', @response.content_type
115
116 assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"}
117 end
118 end
119 end
120
121 context "DELETE /time_entries/2.xml" do
122 should "destroy time entry" do
123 assert_difference 'TimeEntry.count', -1 do
124 delete '/time_entries/2.xml', {}, :authorization => credentials('jsmith')
125 end
126 assert_response :ok
127 assert_nil TimeEntry.find_by_id(2)
128 end
129 end
130
131 def credentials(user, password=nil)
132 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
133 end
134 end
@@ -1,5 +1,5
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
@@ -18,10 +18,11
18 18 class TimelogController < ApplicationController
19 19 menu_item :issues
20 20 before_filter :find_project, :only => [:new, :create]
21 before_filter :find_time_entry, :only => [:edit, :update, :destroy]
21 before_filter :find_time_entry, :only => [:show, :edit, :update, :destroy]
22 22 before_filter :authorize, :except => [:index]
23 23 before_filter :find_optional_project, :only => [:index]
24
24 accept_key_auth :index, :show, :create, :update, :destroy
25
25 26 helper :sort
26 27 include SortHelper
27 28 helper :issues
@@ -66,6 +67,18 class TimelogController < ApplicationController
66 67
67 68 render :layout => !request.xhr?
68 69 }
70 format.api {
71 @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions)
72 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
73 @entries = TimeEntry.find(:all,
74 :include => [:project, :activity, :user, {:issue => :tracker}],
75 :conditions => cond.conditions,
76 :order => sort_clause,
77 :limit => @entry_pages.items_per_page,
78 :offset => @entry_pages.current.offset)
79
80 render :template => 'timelog/index.apit'
81 }
69 82 format.atom {
70 83 entries = TimeEntry.find(:all,
71 84 :include => [:project, :activity, :user, {:issue => :tracker}],
@@ -85,6 +98,14 class TimelogController < ApplicationController
85 98 end
86 99 end
87 100 end
101
102 def show
103 respond_to do |format|
104 # TODO: Implement html response
105 format.html { render :nothing => true, :status => 406 }
106 format.api { render :template => 'timelog/show.apit' }
107 end
108 end
88 109
89 110 def new
90 111 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@@ -102,10 +123,18 class TimelogController < ApplicationController
102 123 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
103 124
104 125 if @time_entry.save
105 flash[:notice] = l(:notice_successful_update)
106 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
126 respond_to do |format|
127 format.html {
128 flash[:notice] = l(:notice_successful_update)
129 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
130 }
131 format.api { render :template => 'timelog/show.apit', :status => :created, :location => time_entry_url(@time_entry) }
132 end
107 133 else
108 render :action => 'edit'
134 respond_to do |format|
135 format.html { render :action => 'edit' }
136 format.api { render_validation_errors(@time_entry) }
137 end
109 138 end
110 139 end
111 140
@@ -122,21 +151,40 class TimelogController < ApplicationController
122 151 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
123 152
124 153 if @time_entry.save
125 flash[:notice] = l(:notice_successful_update)
126 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
154 respond_to do |format|
155 format.html {
156 flash[:notice] = l(:notice_successful_update)
157 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
158 }
159 format.api { head :ok }
160 end
127 161 else
128 render :action => 'edit'
162 respond_to do |format|
163 format.html { render :action => 'edit' }
164 format.api { render_validation_errors(@time_entry) }
165 end
129 166 end
130 167 end
131 168
132 169 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
133 170 def destroy
134 171 if @time_entry.destroy && @time_entry.destroyed?
135 flash[:notice] = l(:notice_successful_delete)
172 respond_to do |format|
173 format.html {
174 flash[:notice] = l(:notice_successful_delete)
175 redirect_to :back
176 }
177 format.api { head :ok }
178 end
136 179 else
137 flash[:error] = l(:notice_unable_delete_time_entry)
180 respond_to do |format|
181 format.html {
182 flash[:notice] = l(:notice_unable_delete_time_entry)
183 redirect_to :back
184 }
185 format.api { render_validation_errors(@time_entry) }
186 end
138 187 end
139 redirect_to :back
140 188 rescue ::ActionController::RedirectBackError
141 189 redirect_to :action => 'index', :project_id => @time_entry.project
142 190 end
@@ -154,11 +202,11 private
154 202 end
155 203
156 204 def find_project
157 if params[:issue_id]
158 @issue = Issue.find(params[:issue_id])
205 if issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])
206 @issue = Issue.find(issue_id)
159 207 @project = @issue.project
160 elsif params[:project_id]
161 @project = Project.find(params[:project_id])
208 elsif project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])
209 @project = Project.find(project_id)
162 210 else
163 211 render_404
164 212 return false
@@ -85,7 +85,7 Redmine::AccessControl.map do |map|
85 85
86 86 map.project_module :time_tracking do |map|
87 87 map.permission :log_time, {:timelog => [:new, :create, :edit, :update]}, :require => :loggedin
88 map.permission :view_time_entries, :timelog => [:index], :time_entry_reports => [:report]
88 map.permission :view_time_entries, :timelog => [:index, :show], :time_entry_reports => [:report]
89 89 map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :member
90 90 map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
91 91 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
General Comments 0
You need to be logged in to leave comments. Login now