##// END OF EJS Templates
Fixed: GET /time_entries.xml ignores limit/offset parameters (#8356)....
Jean-Philippe Lang -
r5761:12801ca031c0
parent child
Show More
@@ -1,323 +1,323
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20 before_filter :find_project, :only => [:new, :create]
20 before_filter :find_project, :only => [:new, :create]
21 before_filter :find_time_entry, :only => [:show, :edit, :update]
21 before_filter :find_time_entry, :only => [:show, :edit, :update]
22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :authorize, :except => [:index]
23 before_filter :authorize, :except => [:index]
24 before_filter :find_optional_project, :only => [:index]
24 before_filter :find_optional_project, :only => [:index]
25 accept_key_auth :index, :show, :create, :update, :destroy
25 accept_key_auth :index, :show, :create, :update, :destroy
26
26
27 helper :sort
27 helper :sort
28 include SortHelper
28 include SortHelper
29 helper :issues
29 helper :issues
30 include TimelogHelper
30 include TimelogHelper
31 helper :custom_fields
31 helper :custom_fields
32 include CustomFieldsHelper
32 include CustomFieldsHelper
33
33
34 def index
34 def index
35 sort_init 'spent_on', 'desc'
35 sort_init 'spent_on', 'desc'
36 sort_update 'spent_on' => 'spent_on',
36 sort_update 'spent_on' => 'spent_on',
37 'user' => 'user_id',
37 'user' => 'user_id',
38 'activity' => 'activity_id',
38 'activity' => 'activity_id',
39 'project' => "#{Project.table_name}.name",
39 'project' => "#{Project.table_name}.name",
40 'issue' => 'issue_id',
40 'issue' => 'issue_id',
41 'hours' => 'hours'
41 'hours' => 'hours'
42
42
43 cond = ARCondition.new
43 cond = ARCondition.new
44 if @issue
44 if @issue
45 cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
45 cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
46 elsif @project
46 elsif @project
47 cond << @project.project_condition(Setting.display_subprojects_issues?)
47 cond << @project.project_condition(Setting.display_subprojects_issues?)
48 end
48 end
49
49
50 retrieve_date_range
50 retrieve_date_range
51 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
51 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
52
52
53 respond_to do |format|
53 respond_to do |format|
54 format.html {
54 format.html {
55 # Paginate results
55 # Paginate results
56 @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
56 @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
57 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
57 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
58 @entries = TimeEntry.visible.find(:all,
58 @entries = TimeEntry.visible.find(:all,
59 :include => [:project, :activity, :user, {:issue => :tracker}],
59 :include => [:project, :activity, :user, {:issue => :tracker}],
60 :conditions => cond.conditions,
60 :conditions => cond.conditions,
61 :order => sort_clause,
61 :order => sort_clause,
62 :limit => @entry_pages.items_per_page,
62 :limit => @entry_pages.items_per_page,
63 :offset => @entry_pages.current.offset)
63 :offset => @entry_pages.current.offset)
64 @total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
64 @total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
65
65
66 render :layout => !request.xhr?
66 render :layout => !request.xhr?
67 }
67 }
68 format.api {
68 format.api {
69 @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
69 @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
70 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
70 @offset, @limit = api_offset_and_limit
71 @entries = TimeEntry.visible.find(:all,
71 @entries = TimeEntry.visible.find(:all,
72 :include => [:project, :activity, :user, {:issue => :tracker}],
72 :include => [:project, :activity, :user, {:issue => :tracker}],
73 :conditions => cond.conditions,
73 :conditions => cond.conditions,
74 :order => sort_clause,
74 :order => sort_clause,
75 :limit => @entry_pages.items_per_page,
75 :limit => @limit,
76 :offset => @entry_pages.current.offset)
76 :offset => @offset)
77 }
77 }
78 format.atom {
78 format.atom {
79 entries = TimeEntry.visible.find(:all,
79 entries = TimeEntry.visible.find(:all,
80 :include => [:project, :activity, :user, {:issue => :tracker}],
80 :include => [:project, :activity, :user, {:issue => :tracker}],
81 :conditions => cond.conditions,
81 :conditions => cond.conditions,
82 :order => "#{TimeEntry.table_name}.created_on DESC",
82 :order => "#{TimeEntry.table_name}.created_on DESC",
83 :limit => Setting.feeds_limit.to_i)
83 :limit => Setting.feeds_limit.to_i)
84 render_feed(entries, :title => l(:label_spent_time))
84 render_feed(entries, :title => l(:label_spent_time))
85 }
85 }
86 format.csv {
86 format.csv {
87 # Export all entries
87 # Export all entries
88 @entries = TimeEntry.visible.find(:all,
88 @entries = TimeEntry.visible.find(:all,
89 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
89 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
90 :conditions => cond.conditions,
90 :conditions => cond.conditions,
91 :order => sort_clause)
91 :order => sort_clause)
92 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
92 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
93 }
93 }
94 end
94 end
95 end
95 end
96
96
97 def show
97 def show
98 respond_to do |format|
98 respond_to do |format|
99 # TODO: Implement html response
99 # TODO: Implement html response
100 format.html { render :nothing => true, :status => 406 }
100 format.html { render :nothing => true, :status => 406 }
101 format.api
101 format.api
102 end
102 end
103 end
103 end
104
104
105 def new
105 def new
106 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
106 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
107 @time_entry.attributes = params[:time_entry]
107 @time_entry.attributes = params[:time_entry]
108
108
109 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
109 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
110 render :action => 'edit'
110 render :action => 'edit'
111 end
111 end
112
112
113 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
113 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
114 def create
114 def create
115 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
115 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
116 @time_entry.attributes = params[:time_entry]
116 @time_entry.attributes = params[:time_entry]
117
117
118 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
118 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
119
119
120 if @time_entry.save
120 if @time_entry.save
121 respond_to do |format|
121 respond_to do |format|
122 format.html {
122 format.html {
123 flash[:notice] = l(:notice_successful_update)
123 flash[:notice] = l(:notice_successful_update)
124 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
124 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
125 }
125 }
126 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
126 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
127 end
127 end
128 else
128 else
129 respond_to do |format|
129 respond_to do |format|
130 format.html { render :action => 'edit' }
130 format.html { render :action => 'edit' }
131 format.api { render_validation_errors(@time_entry) }
131 format.api { render_validation_errors(@time_entry) }
132 end
132 end
133 end
133 end
134 end
134 end
135
135
136 def edit
136 def edit
137 @time_entry.attributes = params[:time_entry]
137 @time_entry.attributes = params[:time_entry]
138
138
139 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
139 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
140 end
140 end
141
141
142 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
142 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
143 def update
143 def update
144 @time_entry.attributes = params[:time_entry]
144 @time_entry.attributes = params[:time_entry]
145
145
146 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
146 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
147
147
148 if @time_entry.save
148 if @time_entry.save
149 respond_to do |format|
149 respond_to do |format|
150 format.html {
150 format.html {
151 flash[:notice] = l(:notice_successful_update)
151 flash[:notice] = l(:notice_successful_update)
152 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
152 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
153 }
153 }
154 format.api { head :ok }
154 format.api { head :ok }
155 end
155 end
156 else
156 else
157 respond_to do |format|
157 respond_to do |format|
158 format.html { render :action => 'edit' }
158 format.html { render :action => 'edit' }
159 format.api { render_validation_errors(@time_entry) }
159 format.api { render_validation_errors(@time_entry) }
160 end
160 end
161 end
161 end
162 end
162 end
163
163
164 def bulk_edit
164 def bulk_edit
165 @available_activities = TimeEntryActivity.shared.active
165 @available_activities = TimeEntryActivity.shared.active
166 @custom_fields = TimeEntry.first.available_custom_fields
166 @custom_fields = TimeEntry.first.available_custom_fields
167 end
167 end
168
168
169 def bulk_update
169 def bulk_update
170 attributes = parse_params_for_bulk_time_entry_attributes(params)
170 attributes = parse_params_for_bulk_time_entry_attributes(params)
171
171
172 unsaved_time_entry_ids = []
172 unsaved_time_entry_ids = []
173 @time_entries.each do |time_entry|
173 @time_entries.each do |time_entry|
174 time_entry.reload
174 time_entry.reload
175 time_entry.attributes = attributes
175 time_entry.attributes = attributes
176 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
176 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
177 unless time_entry.save
177 unless time_entry.save
178 # Keep unsaved time_entry ids to display them in flash error
178 # Keep unsaved time_entry ids to display them in flash error
179 unsaved_time_entry_ids << time_entry.id
179 unsaved_time_entry_ids << time_entry.id
180 end
180 end
181 end
181 end
182 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
182 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
183 redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
183 redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
184 end
184 end
185
185
186 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
186 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
187 def destroy
187 def destroy
188 @time_entries.each do |t|
188 @time_entries.each do |t|
189 begin
189 begin
190 unless t.destroy && t.destroyed?
190 unless t.destroy && t.destroyed?
191 respond_to do |format|
191 respond_to do |format|
192 format.html {
192 format.html {
193 flash[:error] = l(:notice_unable_delete_time_entry)
193 flash[:error] = l(:notice_unable_delete_time_entry)
194 redirect_to :back
194 redirect_to :back
195 }
195 }
196 format.api { render_validation_errors(t) }
196 format.api { render_validation_errors(t) }
197 end
197 end
198 return
198 return
199 end
199 end
200 rescue ::ActionController::RedirectBackError
200 rescue ::ActionController::RedirectBackError
201 redirect_to :action => 'index', :project_id => @projects.first
201 redirect_to :action => 'index', :project_id => @projects.first
202 return
202 return
203 end
203 end
204 end
204 end
205
205
206 respond_to do |format|
206 respond_to do |format|
207 format.html {
207 format.html {
208 flash[:notice] = l(:notice_successful_delete)
208 flash[:notice] = l(:notice_successful_delete)
209 redirect_back_or_default(:action => 'index', :project_id => @projects.first)
209 redirect_back_or_default(:action => 'index', :project_id => @projects.first)
210 }
210 }
211 format.api { head :ok }
211 format.api { head :ok }
212 end
212 end
213 end
213 end
214
214
215 private
215 private
216 def find_time_entry
216 def find_time_entry
217 @time_entry = TimeEntry.find(params[:id])
217 @time_entry = TimeEntry.find(params[:id])
218 unless @time_entry.editable_by?(User.current)
218 unless @time_entry.editable_by?(User.current)
219 render_403
219 render_403
220 return false
220 return false
221 end
221 end
222 @project = @time_entry.project
222 @project = @time_entry.project
223 rescue ActiveRecord::RecordNotFound
223 rescue ActiveRecord::RecordNotFound
224 render_404
224 render_404
225 end
225 end
226
226
227 def find_time_entries
227 def find_time_entries
228 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
228 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
229 raise ActiveRecord::RecordNotFound if @time_entries.empty?
229 raise ActiveRecord::RecordNotFound if @time_entries.empty?
230 @projects = @time_entries.collect(&:project).compact.uniq
230 @projects = @time_entries.collect(&:project).compact.uniq
231 @project = @projects.first if @projects.size == 1
231 @project = @projects.first if @projects.size == 1
232 rescue ActiveRecord::RecordNotFound
232 rescue ActiveRecord::RecordNotFound
233 render_404
233 render_404
234 end
234 end
235
235
236 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
236 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
237 if unsaved_time_entry_ids.empty?
237 if unsaved_time_entry_ids.empty?
238 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
238 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
239 else
239 else
240 flash[:error] = l(:notice_failed_to_save_time_entries,
240 flash[:error] = l(:notice_failed_to_save_time_entries,
241 :count => unsaved_time_entry_ids.size,
241 :count => unsaved_time_entry_ids.size,
242 :total => time_entries.size,
242 :total => time_entries.size,
243 :ids => '#' + unsaved_time_entry_ids.join(', #'))
243 :ids => '#' + unsaved_time_entry_ids.join(', #'))
244 end
244 end
245 end
245 end
246
246
247 def find_project
247 def find_project
248 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
248 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
249 @issue = Issue.find(issue_id)
249 @issue = Issue.find(issue_id)
250 @project = @issue.project
250 @project = @issue.project
251 elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
251 elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
252 @project = Project.find(project_id)
252 @project = Project.find(project_id)
253 else
253 else
254 render_404
254 render_404
255 return false
255 return false
256 end
256 end
257 rescue ActiveRecord::RecordNotFound
257 rescue ActiveRecord::RecordNotFound
258 render_404
258 render_404
259 end
259 end
260
260
261 def find_optional_project
261 def find_optional_project
262 if !params[:issue_id].blank?
262 if !params[:issue_id].blank?
263 @issue = Issue.find(params[:issue_id])
263 @issue = Issue.find(params[:issue_id])
264 @project = @issue.project
264 @project = @issue.project
265 elsif !params[:project_id].blank?
265 elsif !params[:project_id].blank?
266 @project = Project.find(params[:project_id])
266 @project = Project.find(params[:project_id])
267 end
267 end
268 deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
268 deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
269 end
269 end
270
270
271 # Retrieves the date range based on predefined ranges or specific from/to param dates
271 # Retrieves the date range based on predefined ranges or specific from/to param dates
272 def retrieve_date_range
272 def retrieve_date_range
273 @free_period = false
273 @free_period = false
274 @from, @to = nil, nil
274 @from, @to = nil, nil
275
275
276 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
276 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
277 case params[:period].to_s
277 case params[:period].to_s
278 when 'today'
278 when 'today'
279 @from = @to = Date.today
279 @from = @to = Date.today
280 when 'yesterday'
280 when 'yesterday'
281 @from = @to = Date.today - 1
281 @from = @to = Date.today - 1
282 when 'current_week'
282 when 'current_week'
283 @from = Date.today - (Date.today.cwday - 1)%7
283 @from = Date.today - (Date.today.cwday - 1)%7
284 @to = @from + 6
284 @to = @from + 6
285 when 'last_week'
285 when 'last_week'
286 @from = Date.today - 7 - (Date.today.cwday - 1)%7
286 @from = Date.today - 7 - (Date.today.cwday - 1)%7
287 @to = @from + 6
287 @to = @from + 6
288 when '7_days'
288 when '7_days'
289 @from = Date.today - 7
289 @from = Date.today - 7
290 @to = Date.today
290 @to = Date.today
291 when 'current_month'
291 when 'current_month'
292 @from = Date.civil(Date.today.year, Date.today.month, 1)
292 @from = Date.civil(Date.today.year, Date.today.month, 1)
293 @to = (@from >> 1) - 1
293 @to = (@from >> 1) - 1
294 when 'last_month'
294 when 'last_month'
295 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
295 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
296 @to = (@from >> 1) - 1
296 @to = (@from >> 1) - 1
297 when '30_days'
297 when '30_days'
298 @from = Date.today - 30
298 @from = Date.today - 30
299 @to = Date.today
299 @to = Date.today
300 when 'current_year'
300 when 'current_year'
301 @from = Date.civil(Date.today.year, 1, 1)
301 @from = Date.civil(Date.today.year, 1, 1)
302 @to = Date.civil(Date.today.year, 12, 31)
302 @to = Date.civil(Date.today.year, 12, 31)
303 end
303 end
304 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
304 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
305 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
305 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
306 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
306 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
307 @free_period = true
307 @free_period = true
308 else
308 else
309 # default
309 # default
310 end
310 end
311
311
312 @from, @to = @to, @from if @from && @to && @from > @to
312 @from, @to = @to, @from if @from && @to && @from > @to
313 @from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
313 @from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
314 @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
314 @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
315 end
315 end
316
316
317 def parse_params_for_bulk_time_entry_attributes(params)
317 def parse_params_for_bulk_time_entry_attributes(params)
318 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
318 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
319 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
319 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
320 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
320 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
321 attributes
321 attributes
322 end
322 end
323 end
323 end
@@ -1,16 +1,16
1 api.array :time_entries do
1 api.array :time_entries, api_meta(:total_count => @entry_count, :offset => @offset, :limit => @limit) do
2 @entries.each do |time_entry|
2 @entries.each do |time_entry|
3 api.time_entry do
3 api.time_entry do
4 api.id time_entry.id
4 api.id time_entry.id
5 api.project(:id => time_entry.project_id, :name => time_entry.project.name) unless time_entry.project.nil?
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?
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?
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?
8 api.activity(:id => time_entry.activity_id, :name => time_entry.activity.name) unless time_entry.activity.nil?
9 api.hours time_entry.hours
9 api.hours time_entry.hours
10 api.comments time_entry.comments
10 api.comments time_entry.comments
11 api.spent_on time_entry.spent_on
11 api.spent_on time_entry.spent_on
12 api.created_on time_entry.created_on
12 api.created_on time_entry.created_on
13 api.updated_on time_entry.updated_on
13 api.updated_on time_entry.updated_on
14 end
14 end
15 end
15 end
16 end
16 end
@@ -1,134 +1,144
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class ApiTest::TimeEntriesTest < ActionController::IntegrationTest
20 class ApiTest::TimeEntriesTest < ActionController::IntegrationTest
21 fixtures :all
21 fixtures :all
22
22
23 def setup
23 def setup
24 Setting.rest_api_enabled = '1'
24 Setting.rest_api_enabled = '1'
25 end
25 end
26
26
27 context "GET /time_entries.xml" do
27 context "GET /time_entries.xml" do
28 should "return time entries" do
28 should "return time entries" do
29 get '/time_entries.xml', {}, :authorization => credentials('jsmith')
29 get '/time_entries.xml', {}, :authorization => credentials('jsmith')
30 assert_response :success
30 assert_response :success
31 assert_equal 'application/xml', @response.content_type
31 assert_equal 'application/xml', @response.content_type
32 assert_tag :tag => 'time_entries',
32 assert_tag :tag => 'time_entries',
33 :child => {:tag => 'time_entry', :child => {:tag => 'id', :content => '2'}}
33 :child => {:tag => 'time_entry', :child => {:tag => 'id', :content => '2'}}
34 end
34 end
35
36 context "with limit" do
37 should "return limited results" do
38 get '/time_entries.xml?limit=2', {}, :authorization => credentials('jsmith')
39 assert_response :success
40 assert_equal 'application/xml', @response.content_type
41 assert_tag :tag => 'time_entries',
42 :children => {:count => 2}
43 end
44 end
35 end
45 end
36
46
37 context "GET /time_entries/2.xml" do
47 context "GET /time_entries/2.xml" do
38 should "return requested time entry" do
48 should "return requested time entry" do
39 get '/time_entries/2.xml', {}, :authorization => credentials('jsmith')
49 get '/time_entries/2.xml', {}, :authorization => credentials('jsmith')
40 assert_response :success
50 assert_response :success
41 assert_equal 'application/xml', @response.content_type
51 assert_equal 'application/xml', @response.content_type
42 assert_tag :tag => 'time_entry',
52 assert_tag :tag => 'time_entry',
43 :child => {:tag => 'id', :content => '2'}
53 :child => {:tag => 'id', :content => '2'}
44 end
54 end
45 end
55 end
46
56
47 context "POST /time_entries.xml" do
57 context "POST /time_entries.xml" do
48 context "with issue_id" do
58 context "with issue_id" do
49 should "return create time entry" do
59 should "return create time entry" do
50 assert_difference 'TimeEntry.count' do
60 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')
61 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
62 end
53 assert_response :created
63 assert_response :created
54 assert_equal 'application/xml', @response.content_type
64 assert_equal 'application/xml', @response.content_type
55
65
56 entry = TimeEntry.first(:order => 'id DESC')
66 entry = TimeEntry.first(:order => 'id DESC')
57 assert_equal 'jsmith', entry.user.login
67 assert_equal 'jsmith', entry.user.login
58 assert_equal Issue.find(1), entry.issue
68 assert_equal Issue.find(1), entry.issue
59 assert_equal Project.find(1), entry.project
69 assert_equal Project.find(1), entry.project
60 assert_equal Date.parse('2010-12-02'), entry.spent_on
70 assert_equal Date.parse('2010-12-02'), entry.spent_on
61 assert_equal 3.5, entry.hours
71 assert_equal 3.5, entry.hours
62 assert_equal TimeEntryActivity.find(11), entry.activity
72 assert_equal TimeEntryActivity.find(11), entry.activity
63 end
73 end
64 end
74 end
65
75
66 context "with project_id" do
76 context "with project_id" do
67 should "return create time entry" do
77 should "return create time entry" do
68 assert_difference 'TimeEntry.count' do
78 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')
79 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
80 end
71 assert_response :created
81 assert_response :created
72 assert_equal 'application/xml', @response.content_type
82 assert_equal 'application/xml', @response.content_type
73
83
74 entry = TimeEntry.first(:order => 'id DESC')
84 entry = TimeEntry.first(:order => 'id DESC')
75 assert_equal 'jsmith', entry.user.login
85 assert_equal 'jsmith', entry.user.login
76 assert_nil entry.issue
86 assert_nil entry.issue
77 assert_equal Project.find(1), entry.project
87 assert_equal Project.find(1), entry.project
78 assert_equal Date.parse('2010-12-02'), entry.spent_on
88 assert_equal Date.parse('2010-12-02'), entry.spent_on
79 assert_equal 3.5, entry.hours
89 assert_equal 3.5, entry.hours
80 assert_equal TimeEntryActivity.find(11), entry.activity
90 assert_equal TimeEntryActivity.find(11), entry.activity
81 end
91 end
82 end
92 end
83
93
84 context "with invalid parameters" do
94 context "with invalid parameters" do
85 should "return errors" do
95 should "return errors" do
86 assert_no_difference 'TimeEntry.count' do
96 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')
97 post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, :authorization => credentials('jsmith')
88 end
98 end
89 assert_response :unprocessable_entity
99 assert_response :unprocessable_entity
90 assert_equal 'application/xml', @response.content_type
100 assert_equal 'application/xml', @response.content_type
91
101
92 assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"}
102 assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"}
93 end
103 end
94 end
104 end
95 end
105 end
96
106
97 context "PUT /time_entries/2.xml" do
107 context "PUT /time_entries/2.xml" do
98 context "with valid parameters" do
108 context "with valid parameters" do
99 should "update time entry" do
109 should "update time entry" do
100 assert_no_difference 'TimeEntry.count' do
110 assert_no_difference 'TimeEntry.count' do
101 put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, :authorization => credentials('jsmith')
111 put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, :authorization => credentials('jsmith')
102 end
112 end
103 assert_response :ok
113 assert_response :ok
104 assert_equal 'API Update', TimeEntry.find(2).comments
114 assert_equal 'API Update', TimeEntry.find(2).comments
105 end
115 end
106 end
116 end
107
117
108 context "with invalid parameters" do
118 context "with invalid parameters" do
109 should "return errors" do
119 should "return errors" do
110 assert_no_difference 'TimeEntry.count' do
120 assert_no_difference 'TimeEntry.count' do
111 put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, :authorization => credentials('jsmith')
121 put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, :authorization => credentials('jsmith')
112 end
122 end
113 assert_response :unprocessable_entity
123 assert_response :unprocessable_entity
114 assert_equal 'application/xml', @response.content_type
124 assert_equal 'application/xml', @response.content_type
115
125
116 assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"}
126 assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"}
117 end
127 end
118 end
128 end
119 end
129 end
120
130
121 context "DELETE /time_entries/2.xml" do
131 context "DELETE /time_entries/2.xml" do
122 should "destroy time entry" do
132 should "destroy time entry" do
123 assert_difference 'TimeEntry.count', -1 do
133 assert_difference 'TimeEntry.count', -1 do
124 delete '/time_entries/2.xml', {}, :authorization => credentials('jsmith')
134 delete '/time_entries/2.xml', {}, :authorization => credentials('jsmith')
125 end
135 end
126 assert_response :ok
136 assert_response :ok
127 assert_nil TimeEntry.find_by_id(2)
137 assert_nil TimeEntry.find_by_id(2)
128 end
138 end
129 end
139 end
130
140
131 def credentials(user, password=nil)
141 def credentials(user, password=nil)
132 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
142 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
133 end
143 end
134 end
144 end
General Comments 0
You need to be logged in to leave comments. Login now