##// END OF EJS Templates
Fixed that time entries should be sorted by date and created_on (#11178)....
Jean-Philippe Lang -
r9654:2c07b478bb1e
parent child
Show More
@@ -1,344 +1,344
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class TimelogController < ApplicationController
19 19 menu_item :issues
20 20
21 21 before_filter :find_project_for_new_time_entry, :only => [:create]
22 22 before_filter :find_time_entry, :only => [:show, :edit, :update]
23 23 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
24 24 before_filter :authorize, :except => [:new, :index, :report]
25 25
26 26 before_filter :find_optional_project, :only => [:index, :report]
27 27 before_filter :find_optional_project_for_new_time_entry, :only => [:new]
28 28 before_filter :authorize_global, :only => [:new, :index, :report]
29 29
30 30 accept_rss_auth :index
31 31 accept_api_auth :index, :show, :create, :update, :destroy
32 32
33 33 helper :sort
34 34 include SortHelper
35 35 helper :issues
36 36 include TimelogHelper
37 37 helper :custom_fields
38 38 include CustomFieldsHelper
39 39
40 40 def index
41 41 sort_init 'spent_on', 'desc'
42 sort_update 'spent_on' => 'spent_on',
42 sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"],
43 43 'user' => 'user_id',
44 44 'activity' => 'activity_id',
45 45 'project' => "#{Project.table_name}.name",
46 46 'issue' => 'issue_id',
47 47 'hours' => 'hours'
48 48
49 49 retrieve_date_range
50 50
51 51 scope = TimeEntry.visible.spent_between(@from, @to)
52 52 if @issue
53 53 scope = scope.on_issue(@issue)
54 54 elsif @project
55 55 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
56 56 end
57 57
58 58 respond_to do |format|
59 59 format.html {
60 60 # Paginate results
61 61 @entry_count = scope.count
62 62 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
63 63 @entries = scope.all(
64 64 :include => [:project, :activity, :user, {:issue => :tracker}],
65 65 :order => sort_clause,
66 66 :limit => @entry_pages.items_per_page,
67 67 :offset => @entry_pages.current.offset
68 68 )
69 69 @total_hours = scope.sum(:hours).to_f
70 70
71 71 render :layout => !request.xhr?
72 72 }
73 73 format.api {
74 74 @entry_count = scope.count
75 75 @offset, @limit = api_offset_and_limit
76 76 @entries = scope.all(
77 77 :include => [:project, :activity, :user, {:issue => :tracker}],
78 78 :order => sort_clause,
79 79 :limit => @limit,
80 80 :offset => @offset
81 81 )
82 82 }
83 83 format.atom {
84 84 entries = scope.all(
85 85 :include => [:project, :activity, :user, {:issue => :tracker}],
86 86 :order => "#{TimeEntry.table_name}.created_on DESC",
87 87 :limit => Setting.feeds_limit.to_i
88 88 )
89 89 render_feed(entries, :title => l(:label_spent_time))
90 90 }
91 91 format.csv {
92 92 # Export all entries
93 93 @entries = scope.all(
94 94 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
95 95 :order => sort_clause
96 96 )
97 97 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
98 98 }
99 99 end
100 100 end
101 101
102 102 def report
103 103 retrieve_date_range
104 104 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
105 105
106 106 respond_to do |format|
107 107 format.html { render :layout => !request.xhr? }
108 108 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
109 109 end
110 110 end
111 111
112 112 def show
113 113 respond_to do |format|
114 114 # TODO: Implement html response
115 115 format.html { render :nothing => true, :status => 406 }
116 116 format.api
117 117 end
118 118 end
119 119
120 120 def new
121 121 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
122 122 @time_entry.safe_attributes = params[:time_entry]
123 123 end
124 124
125 125 def create
126 126 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
127 127 @time_entry.safe_attributes = params[:time_entry]
128 128
129 129 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
130 130
131 131 if @time_entry.save
132 132 respond_to do |format|
133 133 format.html {
134 134 flash[:notice] = l(:notice_successful_create)
135 135 if params[:continue]
136 136 if params[:project_id]
137 137 redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue,
138 138 :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
139 139 :back_url => params[:back_url]
140 140 else
141 141 redirect_to :action => 'new',
142 142 :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
143 143 :back_url => params[:back_url]
144 144 end
145 145 else
146 146 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
147 147 end
148 148 }
149 149 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
150 150 end
151 151 else
152 152 respond_to do |format|
153 153 format.html { render :action => 'new' }
154 154 format.api { render_validation_errors(@time_entry) }
155 155 end
156 156 end
157 157 end
158 158
159 159 def edit
160 160 @time_entry.safe_attributes = params[:time_entry]
161 161 end
162 162
163 163 def update
164 164 @time_entry.safe_attributes = params[:time_entry]
165 165
166 166 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
167 167
168 168 if @time_entry.save
169 169 respond_to do |format|
170 170 format.html {
171 171 flash[:notice] = l(:notice_successful_update)
172 172 redirect_back_or_default :action => 'index', :project_id => @time_entry.project
173 173 }
174 174 format.api { head :ok }
175 175 end
176 176 else
177 177 respond_to do |format|
178 178 format.html { render :action => 'edit' }
179 179 format.api { render_validation_errors(@time_entry) }
180 180 end
181 181 end
182 182 end
183 183
184 184 def bulk_edit
185 185 @available_activities = TimeEntryActivity.shared.active
186 186 @custom_fields = TimeEntry.first.available_custom_fields
187 187 end
188 188
189 189 def bulk_update
190 190 attributes = parse_params_for_bulk_time_entry_attributes(params)
191 191
192 192 unsaved_time_entry_ids = []
193 193 @time_entries.each do |time_entry|
194 194 time_entry.reload
195 195 time_entry.safe_attributes = attributes
196 196 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
197 197 unless time_entry.save
198 198 # Keep unsaved time_entry ids to display them in flash error
199 199 unsaved_time_entry_ids << time_entry.id
200 200 end
201 201 end
202 202 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
203 203 redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
204 204 end
205 205
206 206 def destroy
207 207 destroyed = TimeEntry.transaction do
208 208 @time_entries.each do |t|
209 209 unless t.destroy && t.destroyed?
210 210 raise ActiveRecord::Rollback
211 211 end
212 212 end
213 213 end
214 214
215 215 respond_to do |format|
216 216 format.html {
217 217 if destroyed
218 218 flash[:notice] = l(:notice_successful_delete)
219 219 else
220 220 flash[:error] = l(:notice_unable_delete_time_entry)
221 221 end
222 222 redirect_back_or_default(:action => 'index', :project_id => @projects.first)
223 223 }
224 224 format.api {
225 225 if destroyed
226 226 head :ok
227 227 else
228 228 render_validation_errors(@time_entries)
229 229 end
230 230 }
231 231 end
232 232 end
233 233
234 234 private
235 235 def find_time_entry
236 236 @time_entry = TimeEntry.find(params[:id])
237 237 unless @time_entry.editable_by?(User.current)
238 238 render_403
239 239 return false
240 240 end
241 241 @project = @time_entry.project
242 242 rescue ActiveRecord::RecordNotFound
243 243 render_404
244 244 end
245 245
246 246 def find_time_entries
247 247 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
248 248 raise ActiveRecord::RecordNotFound if @time_entries.empty?
249 249 @projects = @time_entries.collect(&:project).compact.uniq
250 250 @project = @projects.first if @projects.size == 1
251 251 rescue ActiveRecord::RecordNotFound
252 252 render_404
253 253 end
254 254
255 255 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
256 256 if unsaved_time_entry_ids.empty?
257 257 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
258 258 else
259 259 flash[:error] = l(:notice_failed_to_save_time_entries,
260 260 :count => unsaved_time_entry_ids.size,
261 261 :total => time_entries.size,
262 262 :ids => '#' + unsaved_time_entry_ids.join(', #'))
263 263 end
264 264 end
265 265
266 266 def find_optional_project_for_new_time_entry
267 267 if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
268 268 @project = Project.find(project_id)
269 269 end
270 270 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
271 271 @issue = Issue.find(issue_id)
272 272 @project ||= @issue.project
273 273 end
274 274 rescue ActiveRecord::RecordNotFound
275 275 render_404
276 276 end
277 277
278 278 def find_project_for_new_time_entry
279 279 find_optional_project_for_new_time_entry
280 280 if @project.nil?
281 281 render_404
282 282 end
283 283 end
284 284
285 285 def find_optional_project
286 286 if !params[:issue_id].blank?
287 287 @issue = Issue.find(params[:issue_id])
288 288 @project = @issue.project
289 289 elsif !params[:project_id].blank?
290 290 @project = Project.find(params[:project_id])
291 291 end
292 292 end
293 293
294 294 # Retrieves the date range based on predefined ranges or specific from/to param dates
295 295 def retrieve_date_range
296 296 @free_period = false
297 297 @from, @to = nil, nil
298 298
299 299 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
300 300 case params[:period].to_s
301 301 when 'today'
302 302 @from = @to = Date.today
303 303 when 'yesterday'
304 304 @from = @to = Date.today - 1
305 305 when 'current_week'
306 306 @from = Date.today - (Date.today.cwday - 1)%7
307 307 @to = @from + 6
308 308 when 'last_week'
309 309 @from = Date.today - 7 - (Date.today.cwday - 1)%7
310 310 @to = @from + 6
311 311 when '7_days'
312 312 @from = Date.today - 7
313 313 @to = Date.today
314 314 when 'current_month'
315 315 @from = Date.civil(Date.today.year, Date.today.month, 1)
316 316 @to = (@from >> 1) - 1
317 317 when 'last_month'
318 318 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
319 319 @to = (@from >> 1) - 1
320 320 when '30_days'
321 321 @from = Date.today - 30
322 322 @to = Date.today
323 323 when 'current_year'
324 324 @from = Date.civil(Date.today.year, 1, 1)
325 325 @to = Date.civil(Date.today.year, 12, 31)
326 326 end
327 327 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
328 328 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
329 329 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
330 330 @free_period = true
331 331 else
332 332 # default
333 333 end
334 334
335 335 @from, @to = @to, @from if @from && @to && @from > @to
336 336 end
337 337
338 338 def parse_params_for_bulk_time_entry_attributes(params)
339 339 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
340 340 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
341 341 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
342 342 attributes
343 343 end
344 344 end
@@ -1,761 +1,775
1 1 # -*- coding: utf-8 -*-
2 2 # Redmine - project management software
3 3 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 4 #
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; either version 2
8 8 # of the License, or (at your option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 18
19 19 require File.expand_path('../../test_helper', __FILE__)
20 20 require 'timelog_controller'
21 21
22 22 # Re-raise errors caught by the controller.
23 23 class TimelogController; def rescue_action(e) raise e end; end
24 24
25 25 class TimelogControllerTest < ActionController::TestCase
26 26 fixtures :projects, :enabled_modules, :roles, :members,
27 27 :member_roles, :issues, :time_entries, :users,
28 28 :trackers, :enumerations, :issue_statuses,
29 29 :custom_fields, :custom_values
30 30
31 31 include Redmine::I18n
32 32
33 33 def setup
34 34 @controller = TimelogController.new
35 35 @request = ActionController::TestRequest.new
36 36 @response = ActionController::TestResponse.new
37 37 end
38 38
39 39 def test_get_new
40 40 @request.session[:user_id] = 3
41 41 get :new, :project_id => 1
42 42 assert_response :success
43 43 assert_template 'new'
44 44 # Default activity selected
45 45 assert_tag :tag => 'option', :attributes => { :selected => 'selected' },
46 46 :content => 'Development'
47 47 assert_select 'input[name=project_id][value=1]'
48 48 end
49 49
50 50 def test_get_new_should_only_show_active_time_entry_activities
51 51 @request.session[:user_id] = 3
52 52 get :new, :project_id => 1
53 53 assert_response :success
54 54 assert_template 'new'
55 55 assert_no_tag 'select', :attributes => {:name => 'time_entry[project_id]'}
56 56 assert_no_tag 'option', :content => 'Inactive Activity'
57 57 end
58 58
59 59 def test_new_without_project
60 60 @request.session[:user_id] = 3
61 61 get :new
62 62 assert_response :success
63 63 assert_template 'new'
64 64 assert_tag 'select', :attributes => {:name => 'time_entry[project_id]'}
65 65 assert_select 'input[name=project_id]', 0
66 66 end
67 67
68 68 def test_new_without_project_should_prefill_the_form
69 69 @request.session[:user_id] = 3
70 70 get :new, :time_entry => {:project_id => '1'}
71 71 assert_response :success
72 72 assert_template 'new'
73 73 assert_select 'select[name=?]', 'time_entry[project_id]' do
74 74 assert_select 'option[value=1][selected=selected]'
75 75 end
76 76 assert_select 'input[name=project_id]', 0
77 77 end
78 78
79 79 def test_new_without_project_should_deny_without_permission
80 80 Role.all.each {|role| role.remove_permission! :log_time}
81 81 @request.session[:user_id] = 3
82 82
83 83 get :new
84 84 assert_response 403
85 85 end
86 86
87 87 def test_get_edit_existing_time
88 88 @request.session[:user_id] = 2
89 89 get :edit, :id => 2, :project_id => nil
90 90 assert_response :success
91 91 assert_template 'edit'
92 92 # Default activity selected
93 93 assert_tag :tag => 'form', :attributes => { :action => '/projects/ecookbook/time_entries/2' }
94 94 end
95 95
96 96 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
97 97 te = TimeEntry.find(1)
98 98 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
99 99 te.save!
100 100
101 101 @request.session[:user_id] = 1
102 102 get :edit, :project_id => 1, :id => 1
103 103 assert_response :success
104 104 assert_template 'edit'
105 105 # Blank option since nothing is pre-selected
106 106 assert_tag :tag => 'option', :content => '--- Please select ---'
107 107 end
108 108
109 109 def test_post_create
110 110 # TODO: should POST to issues’ time log instead of project. change form
111 111 # and routing
112 112 @request.session[:user_id] = 3
113 113 post :create, :project_id => 1,
114 114 :time_entry => {:comments => 'Some work on TimelogControllerTest',
115 115 # Not the default activity
116 116 :activity_id => '11',
117 117 :spent_on => '2008-03-14',
118 118 :issue_id => '1',
119 119 :hours => '7.3'}
120 120 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
121 121
122 122 i = Issue.find(1)
123 123 t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
124 124 assert_not_nil t
125 125 assert_equal 11, t.activity_id
126 126 assert_equal 7.3, t.hours
127 127 assert_equal 3, t.user_id
128 128 assert_equal i, t.issue
129 129 assert_equal i.project, t.project
130 130 end
131 131
132 132 def test_post_create_with_blank_issue
133 133 # TODO: should POST to issues’ time log instead of project. change form
134 134 # and routing
135 135 @request.session[:user_id] = 3
136 136 post :create, :project_id => 1,
137 137 :time_entry => {:comments => 'Some work on TimelogControllerTest',
138 138 # Not the default activity
139 139 :activity_id => '11',
140 140 :issue_id => '',
141 141 :spent_on => '2008-03-14',
142 142 :hours => '7.3'}
143 143 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
144 144
145 145 t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
146 146 assert_not_nil t
147 147 assert_equal 11, t.activity_id
148 148 assert_equal 7.3, t.hours
149 149 assert_equal 3, t.user_id
150 150 end
151 151
152 152 def test_create_and_continue
153 153 @request.session[:user_id] = 2
154 154 post :create, :project_id => 1,
155 155 :time_entry => {:activity_id => '11',
156 156 :issue_id => '',
157 157 :spent_on => '2008-03-14',
158 158 :hours => '7.3'},
159 159 :continue => '1'
160 160 assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D='
161 161 end
162 162
163 163 def test_create_and_continue_with_issue_id
164 164 @request.session[:user_id] = 2
165 165 post :create, :project_id => 1,
166 166 :time_entry => {:activity_id => '11',
167 167 :issue_id => '1',
168 168 :spent_on => '2008-03-14',
169 169 :hours => '7.3'},
170 170 :continue => '1'
171 171 assert_redirected_to '/projects/ecookbook/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1'
172 172 end
173 173
174 174 def test_create_and_continue_without_project
175 175 @request.session[:user_id] = 2
176 176 post :create, :time_entry => {:project_id => '1',
177 177 :activity_id => '11',
178 178 :issue_id => '',
179 179 :spent_on => '2008-03-14',
180 180 :hours => '7.3'},
181 181 :continue => '1'
182 182
183 183 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1'
184 184 end
185 185
186 186 def test_create_without_log_time_permission_should_be_denied
187 187 @request.session[:user_id] = 2
188 188 Role.find_by_name('Manager').remove_permission! :log_time
189 189 post :create, :project_id => 1,
190 190 :time_entry => {:activity_id => '11',
191 191 :issue_id => '',
192 192 :spent_on => '2008-03-14',
193 193 :hours => '7.3'}
194 194
195 195 assert_response 403
196 196 end
197 197
198 198 def test_create_with_failure
199 199 @request.session[:user_id] = 2
200 200 post :create, :project_id => 1,
201 201 :time_entry => {:activity_id => '',
202 202 :issue_id => '',
203 203 :spent_on => '2008-03-14',
204 204 :hours => '7.3'}
205 205
206 206 assert_response :success
207 207 assert_template 'new'
208 208 end
209 209
210 210 def test_create_without_project
211 211 @request.session[:user_id] = 2
212 212 assert_difference 'TimeEntry.count' do
213 213 post :create, :time_entry => {:project_id => '1',
214 214 :activity_id => '11',
215 215 :issue_id => '',
216 216 :spent_on => '2008-03-14',
217 217 :hours => '7.3'}
218 218 end
219 219
220 220 assert_redirected_to '/projects/ecookbook/time_entries'
221 221 time_entry = TimeEntry.first(:order => 'id DESC')
222 222 assert_equal 1, time_entry.project_id
223 223 end
224 224
225 225 def test_create_without_project_should_fail_with_issue_not_inside_project
226 226 @request.session[:user_id] = 2
227 227 assert_no_difference 'TimeEntry.count' do
228 228 post :create, :time_entry => {:project_id => '1',
229 229 :activity_id => '11',
230 230 :issue_id => '5',
231 231 :spent_on => '2008-03-14',
232 232 :hours => '7.3'}
233 233 end
234 234
235 235 assert_response :success
236 236 assert assigns(:time_entry).errors[:issue_id].present?
237 237 end
238 238
239 239 def test_create_without_project_should_deny_without_permission
240 240 @request.session[:user_id] = 2
241 241 Project.find(3).disable_module!(:time_tracking)
242 242
243 243 assert_no_difference 'TimeEntry.count' do
244 244 post :create, :time_entry => {:project_id => '3',
245 245 :activity_id => '11',
246 246 :issue_id => '',
247 247 :spent_on => '2008-03-14',
248 248 :hours => '7.3'}
249 249 end
250 250
251 251 assert_response 403
252 252 end
253 253
254 254 def test_create_without_project_with_failure
255 255 @request.session[:user_id] = 2
256 256 assert_no_difference 'TimeEntry.count' do
257 257 post :create, :time_entry => {:project_id => '1',
258 258 :activity_id => '11',
259 259 :issue_id => '',
260 260 :spent_on => '2008-03-14',
261 261 :hours => ''}
262 262 end
263 263
264 264 assert_response :success
265 265 assert_tag 'select', :attributes => {:name => 'time_entry[project_id]'},
266 266 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}}
267 267 end
268 268
269 269 def test_update
270 270 entry = TimeEntry.find(1)
271 271 assert_equal 1, entry.issue_id
272 272 assert_equal 2, entry.user_id
273 273
274 274 @request.session[:user_id] = 1
275 275 put :update, :id => 1,
276 276 :time_entry => {:issue_id => '2',
277 277 :hours => '8'}
278 278 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
279 279 entry.reload
280 280
281 281 assert_equal 8, entry.hours
282 282 assert_equal 2, entry.issue_id
283 283 assert_equal 2, entry.user_id
284 284 end
285 285
286 286 def test_get_bulk_edit
287 287 @request.session[:user_id] = 2
288 288 get :bulk_edit, :ids => [1, 2]
289 289 assert_response :success
290 290 assert_template 'bulk_edit'
291 291
292 292 # System wide custom field
293 293 assert_tag :select, :attributes => {:name => 'time_entry[custom_field_values][10]'}
294 294
295 295 # Activities
296 296 assert_select 'select[name=?]', 'time_entry[activity_id]' do
297 297 assert_select 'option[value=]', :text => '(No change)'
298 298 assert_select 'option[value=9]', :text => 'Design'
299 299 end
300 300 end
301 301
302 302 def test_get_bulk_edit_on_different_projects
303 303 @request.session[:user_id] = 2
304 304 get :bulk_edit, :ids => [1, 2, 6]
305 305 assert_response :success
306 306 assert_template 'bulk_edit'
307 307 end
308 308
309 309 def test_bulk_update
310 310 @request.session[:user_id] = 2
311 311 # update time entry activity
312 312 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
313 313
314 314 assert_response 302
315 315 # check that the issues were updated
316 316 assert_equal [9, 9], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.activity_id}
317 317 end
318 318
319 319 def test_bulk_update_with_failure
320 320 @request.session[:user_id] = 2
321 321 post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'}
322 322
323 323 assert_response 302
324 324 assert_match /Failed to save 2 time entrie/, flash[:error]
325 325 end
326 326
327 327 def test_bulk_update_on_different_projects
328 328 @request.session[:user_id] = 2
329 329 # makes user a manager on the other project
330 330 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
331 331
332 332 # update time entry activity
333 333 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
334 334
335 335 assert_response 302
336 336 # check that the issues were updated
337 337 assert_equal [9, 9, 9], TimeEntry.find_all_by_id([1, 2, 4]).collect {|i| i.activity_id}
338 338 end
339 339
340 340 def test_bulk_update_on_different_projects_without_rights
341 341 @request.session[:user_id] = 3
342 342 user = User.find(3)
343 343 action = { :controller => "timelog", :action => "bulk_update" }
344 344 assert user.allowed_to?(action, TimeEntry.find(1).project)
345 345 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
346 346 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
347 347 assert_response 403
348 348 end
349 349
350 350 def test_bulk_update_custom_field
351 351 @request.session[:user_id] = 2
352 352 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
353 353
354 354 assert_response 302
355 355 assert_equal ["0", "0"], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.custom_value_for(10).value}
356 356 end
357 357
358 358 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
359 359 @request.session[:user_id] = 2
360 360 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
361 361
362 362 assert_response :redirect
363 363 assert_redirected_to '/time_entries'
364 364 end
365 365
366 366 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
367 367 @request.session[:user_id] = 2
368 368 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
369 369
370 370 assert_response :redirect
371 371 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
372 372 end
373 373
374 374 def test_post_bulk_update_without_edit_permission_should_be_denied
375 375 @request.session[:user_id] = 2
376 376 Role.find_by_name('Manager').remove_permission! :edit_time_entries
377 377 post :bulk_update, :ids => [1,2]
378 378
379 379 assert_response 403
380 380 end
381 381
382 382 def test_destroy
383 383 @request.session[:user_id] = 2
384 384 delete :destroy, :id => 1
385 385 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
386 386 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
387 387 assert_nil TimeEntry.find_by_id(1)
388 388 end
389 389
390 390 def test_destroy_should_fail
391 391 # simulate that this fails (e.g. due to a plugin), see #5700
392 392 TimeEntry.any_instance.expects(:destroy).returns(false)
393 393
394 394 @request.session[:user_id] = 2
395 395 delete :destroy, :id => 1
396 396 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
397 397 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
398 398 assert_not_nil TimeEntry.find_by_id(1)
399 399 end
400 400
401 401 def test_index_all_projects
402 402 get :index
403 403 assert_response :success
404 404 assert_template 'index'
405 405 assert_not_nil assigns(:total_hours)
406 406 assert_equal "162.90", "%.2f" % assigns(:total_hours)
407 407 assert_tag :form,
408 408 :attributes => {:action => "/time_entries", :id => 'query_form'}
409 409 end
410 410
411 411 def test_index_all_projects_should_show_log_time_link
412 412 @request.session[:user_id] = 2
413 413 get :index
414 414 assert_response :success
415 415 assert_template 'index'
416 416 assert_tag 'a', :attributes => {:href => '/time_entries/new'}, :content => /Log time/
417 417 end
418 418
419 419 def test_index_at_project_level
420 420 get :index, :project_id => 'ecookbook'
421 421 assert_response :success
422 422 assert_template 'index'
423 423 assert_not_nil assigns(:entries)
424 424 assert_equal 4, assigns(:entries).size
425 425 # project and subproject
426 426 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
427 427 assert_not_nil assigns(:total_hours)
428 428 assert_equal "162.90", "%.2f" % assigns(:total_hours)
429 429 # display all time by default
430 430 assert_nil assigns(:from)
431 431 assert_nil assigns(:to)
432 432 assert_tag :form,
433 433 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
434 434 end
435 435
436 436 def test_index_at_project_level_with_date_range
437 437 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
438 438 assert_response :success
439 439 assert_template 'index'
440 440 assert_not_nil assigns(:entries)
441 441 assert_equal 3, assigns(:entries).size
442 442 assert_not_nil assigns(:total_hours)
443 443 assert_equal "12.90", "%.2f" % assigns(:total_hours)
444 444 assert_equal '2007-03-20'.to_date, assigns(:from)
445 445 assert_equal '2007-04-30'.to_date, assigns(:to)
446 446 assert_tag :form,
447 447 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
448 448 end
449 449
450 450 def test_index_at_project_level_with_period
451 451 get :index, :project_id => 'ecookbook', :period => '7_days'
452 452 assert_response :success
453 453 assert_template 'index'
454 454 assert_not_nil assigns(:entries)
455 455 assert_not_nil assigns(:total_hours)
456 456 assert_equal Date.today - 7, assigns(:from)
457 457 assert_equal Date.today, assigns(:to)
458 458 assert_tag :form,
459 459 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
460 460 end
461 461
462 462 def test_index_one_day
463 463 get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => "2007-03-23"
464 464 assert_response :success
465 465 assert_template 'index'
466 466 assert_not_nil assigns(:total_hours)
467 467 assert_equal "4.25", "%.2f" % assigns(:total_hours)
468 468 assert_tag :form,
469 469 :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'}
470 470 end
471 471
472 472 def test_index_from_a_date
473 473 get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => ""
474 474 assert_equal '2007-03-23'.to_date, assigns(:from)
475 475 assert_nil assigns(:to)
476 476 end
477 477
478 478 def test_index_to_a_date
479 479 get :index, :project_id => 'ecookbook', :from => "", :to => "2007-03-23"
480 480 assert_nil assigns(:from)
481 481 assert_equal '2007-03-23'.to_date, assigns(:to)
482 482 end
483 483
484 484 def test_index_today
485 485 Date.stubs(:today).returns('2011-12-15'.to_date)
486 486 get :index, :period => 'today'
487 487 assert_equal '2011-12-15'.to_date, assigns(:from)
488 488 assert_equal '2011-12-15'.to_date, assigns(:to)
489 489 end
490 490
491 491 def test_index_yesterday
492 492 Date.stubs(:today).returns('2011-12-15'.to_date)
493 493 get :index, :period => 'yesterday'
494 494 assert_equal '2011-12-14'.to_date, assigns(:from)
495 495 assert_equal '2011-12-14'.to_date, assigns(:to)
496 496 end
497 497
498 498 def test_index_current_week
499 499 Date.stubs(:today).returns('2011-12-15'.to_date)
500 500 get :index, :period => 'current_week'
501 501 assert_equal '2011-12-12'.to_date, assigns(:from)
502 502 assert_equal '2011-12-18'.to_date, assigns(:to)
503 503 end
504 504
505 505 def test_index_last_week
506 506 Date.stubs(:today).returns('2011-12-15'.to_date)
507 507 get :index, :period => 'current_week'
508 508 assert_equal '2011-12-05'.to_date, assigns(:from)
509 509 assert_equal '2011-12-11'.to_date, assigns(:to)
510 510 end
511 511
512 512 def test_index_last_week
513 513 Date.stubs(:today).returns('2011-12-15'.to_date)
514 514 get :index, :period => 'last_week'
515 515 assert_equal '2011-12-05'.to_date, assigns(:from)
516 516 assert_equal '2011-12-11'.to_date, assigns(:to)
517 517 end
518 518
519 519 def test_index_7_days
520 520 Date.stubs(:today).returns('2011-12-15'.to_date)
521 521 get :index, :period => '7_days'
522 522 assert_equal '2011-12-08'.to_date, assigns(:from)
523 523 assert_equal '2011-12-15'.to_date, assigns(:to)
524 524 end
525 525
526 526 def test_index_current_month
527 527 Date.stubs(:today).returns('2011-12-15'.to_date)
528 528 get :index, :period => 'current_month'
529 529 assert_equal '2011-12-01'.to_date, assigns(:from)
530 530 assert_equal '2011-12-31'.to_date, assigns(:to)
531 531 end
532 532
533 533 def test_index_last_month
534 534 Date.stubs(:today).returns('2011-12-15'.to_date)
535 535 get :index, :period => 'last_month'
536 536 assert_equal '2011-11-01'.to_date, assigns(:from)
537 537 assert_equal '2011-11-30'.to_date, assigns(:to)
538 538 end
539 539
540 540 def test_index_30_days
541 541 Date.stubs(:today).returns('2011-12-15'.to_date)
542 542 get :index, :period => '30_days'
543 543 assert_equal '2011-11-15'.to_date, assigns(:from)
544 544 assert_equal '2011-12-15'.to_date, assigns(:to)
545 545 end
546 546
547 547 def test_index_current_year
548 548 Date.stubs(:today).returns('2011-12-15'.to_date)
549 549 get :index, :period => 'current_year'
550 550 assert_equal '2011-01-01'.to_date, assigns(:from)
551 551 assert_equal '2011-12-31'.to_date, assigns(:to)
552 552 end
553 553
554 554 def test_index_at_issue_level
555 555 get :index, :issue_id => 1
556 556 assert_response :success
557 557 assert_template 'index'
558 558 assert_not_nil assigns(:entries)
559 559 assert_equal 2, assigns(:entries).size
560 560 assert_not_nil assigns(:total_hours)
561 561 assert_equal 154.25, assigns(:total_hours)
562 562 # display all time
563 563 assert_nil assigns(:from)
564 564 assert_nil assigns(:to)
565 565 # TODO: remove /projects/:project_id/issues/:issue_id/time_entries routes
566 566 # to use /issues/:issue_id/time_entries
567 567 assert_tag :form,
568 568 :attributes => {:action => "/projects/ecookbook/issues/1/time_entries", :id => 'query_form'}
569 569 end
570 570
571 def test_index_should_sort_by_spent_on_and_created_on
572 t1 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:00:00', :activity_id => 10)
573 t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10)
574 t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10)
575
576 get :index, :project_id => 1, :from => '2012-06-15', :to => '2012-06-16'
577 assert_response :success
578 assert_equal [t2, t1, t3], assigns(:entries)
579
580 get :index, :project_id => 1, :from => '2012-06-15', :to => '2012-06-16', :sort => 'spent_on'
581 assert_response :success
582 assert_equal [t3, t1, t2], assigns(:entries)
583 end
584
571 585 def test_index_atom_feed
572 586 get :index, :project_id => 1, :format => 'atom'
573 587 assert_response :success
574 588 assert_equal 'application/atom+xml', @response.content_type
575 589 assert_not_nil assigns(:items)
576 590 assert assigns(:items).first.is_a?(TimeEntry)
577 591 end
578 592
579 593 def test_index_all_projects_csv_export
580 594 Setting.date_format = '%m/%d/%Y'
581 595 get :index, :format => 'csv'
582 596 assert_response :success
583 597 assert_equal 'text/csv; header=present', @response.content_type
584 598 assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n")
585 599 assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n")
586 600 end
587 601
588 602 def test_index_csv_export
589 603 Setting.date_format = '%m/%d/%Y'
590 604 get :index, :project_id => 1, :format => 'csv'
591 605 assert_response :success
592 606 assert_equal 'text/csv; header=present', @response.content_type
593 607 assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n")
594 608 assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n")
595 609 end
596 610
597 611 def test_index_csv_export_with_multi_custom_field
598 612 field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'list',
599 613 :multiple => true, :possible_values => ['value1', 'value2'])
600 614 entry = TimeEntry.find(1)
601 615 entry.custom_field_values = {field.id => ['value1', 'value2']}
602 616 entry.save!
603 617
604 618 get :index, :project_id => 1, :format => 'csv'
605 619 assert_response :success
606 620 assert_include '"value1, value2"', @response.body
607 621 end
608 622
609 623 def test_csv_big_5
610 624 user = User.find_by_id(3)
611 625 user.language = "zh-TW"
612 626 assert user.save
613 627 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
614 628 str_big5 = "\xa4@\xa4\xeb"
615 629 if str_utf8.respond_to?(:force_encoding)
616 630 str_utf8.force_encoding('UTF-8')
617 631 str_big5.force_encoding('Big5')
618 632 end
619 633 @request.session[:user_id] = 3
620 634 post :create, :project_id => 1,
621 635 :time_entry => {:comments => str_utf8,
622 636 # Not the default activity
623 637 :activity_id => '11',
624 638 :issue_id => '',
625 639 :spent_on => '2011-11-10',
626 640 :hours => '7.3'}
627 641 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
628 642
629 643 t = TimeEntry.find_by_comments(str_utf8)
630 644 assert_not_nil t
631 645 assert_equal 11, t.activity_id
632 646 assert_equal 7.3, t.hours
633 647 assert_equal 3, t.user_id
634 648
635 649 get :index, :project_id => 1, :format => 'csv',
636 650 :from => '2011-11-10', :to => '2011-11-10'
637 651 assert_response :success
638 652 assert_equal 'text/csv; header=present', @response.content_type
639 653 ar = @response.body.chomp.split("\n")
640 654 s1 = "\xa4\xe9\xb4\xc1"
641 655 if str_utf8.respond_to?(:force_encoding)
642 656 s1.force_encoding('Big5')
643 657 end
644 658 assert ar[0].include?(s1)
645 659 assert ar[1].include?(str_big5)
646 660 end
647 661
648 662 def test_csv_cannot_convert_should_be_replaced_big_5
649 663 user = User.find_by_id(3)
650 664 user.language = "zh-TW"
651 665 assert user.save
652 666 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
653 667 if str_utf8.respond_to?(:force_encoding)
654 668 str_utf8.force_encoding('UTF-8')
655 669 end
656 670 @request.session[:user_id] = 3
657 671 post :create, :project_id => 1,
658 672 :time_entry => {:comments => str_utf8,
659 673 # Not the default activity
660 674 :activity_id => '11',
661 675 :issue_id => '',
662 676 :spent_on => '2011-11-10',
663 677 :hours => '7.3'}
664 678 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
665 679
666 680 t = TimeEntry.find_by_comments(str_utf8)
667 681 assert_not_nil t
668 682 assert_equal 11, t.activity_id
669 683 assert_equal 7.3, t.hours
670 684 assert_equal 3, t.user_id
671 685
672 686 get :index, :project_id => 1, :format => 'csv',
673 687 :from => '2011-11-10', :to => '2011-11-10'
674 688 assert_response :success
675 689 assert_equal 'text/csv; header=present', @response.content_type
676 690 ar = @response.body.chomp.split("\n")
677 691 s1 = "\xa4\xe9\xb4\xc1"
678 692 if str_utf8.respond_to?(:force_encoding)
679 693 s1.force_encoding('Big5')
680 694 end
681 695 assert ar[0].include?(s1)
682 696 s2 = ar[1].split(",")[8]
683 697 if s2.respond_to?(:force_encoding)
684 698 s3 = "\xa5H?"
685 699 s3.force_encoding('Big5')
686 700 assert_equal s3, s2
687 701 elsif RUBY_PLATFORM == 'java'
688 702 assert_equal "??", s2
689 703 else
690 704 assert_equal "\xa5H???", s2
691 705 end
692 706 end
693 707
694 708 def test_csv_tw
695 709 with_settings :default_language => "zh-TW" do
696 710 str1 = "test_csv_tw"
697 711 user = User.find_by_id(3)
698 712 te1 = TimeEntry.create(:spent_on => '2011-11-10',
699 713 :hours => 999.9,
700 714 :project => Project.find(1),
701 715 :user => user,
702 716 :activity => TimeEntryActivity.find_by_name('Design'),
703 717 :comments => str1)
704 718 te2 = TimeEntry.find_by_comments(str1)
705 719 assert_not_nil te2
706 720 assert_equal 999.9, te2.hours
707 721 assert_equal 3, te2.user_id
708 722
709 723 get :index, :project_id => 1, :format => 'csv',
710 724 :from => '2011-11-10', :to => '2011-11-10'
711 725 assert_response :success
712 726 assert_equal 'text/csv; header=present', @response.content_type
713 727
714 728 ar = @response.body.chomp.split("\n")
715 729 s2 = ar[1].split(",")[7]
716 730 assert_equal '999.9', s2
717 731
718 732 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
719 733 if str_tw.respond_to?(:force_encoding)
720 734 str_tw.force_encoding('UTF-8')
721 735 end
722 736 assert_equal str_tw, l(:general_lang_name)
723 737 assert_equal ',', l(:general_csv_separator)
724 738 assert_equal '.', l(:general_csv_decimal_separator)
725 739 end
726 740 end
727 741
728 742 def test_csv_fr
729 743 with_settings :default_language => "fr" do
730 744 str1 = "test_csv_fr"
731 745 user = User.find_by_id(3)
732 746 te1 = TimeEntry.create(:spent_on => '2011-11-10',
733 747 :hours => 999.9,
734 748 :project => Project.find(1),
735 749 :user => user,
736 750 :activity => TimeEntryActivity.find_by_name('Design'),
737 751 :comments => str1)
738 752 te2 = TimeEntry.find_by_comments(str1)
739 753 assert_not_nil te2
740 754 assert_equal 999.9, te2.hours
741 755 assert_equal 3, te2.user_id
742 756
743 757 get :index, :project_id => 1, :format => 'csv',
744 758 :from => '2011-11-10', :to => '2011-11-10'
745 759 assert_response :success
746 760 assert_equal 'text/csv; header=present', @response.content_type
747 761
748 762 ar = @response.body.chomp.split("\n")
749 763 s2 = ar[1].split(";")[7]
750 764 assert_equal '999,9', s2
751 765
752 766 str_fr = "Fran\xc3\xa7ais"
753 767 if str_fr.respond_to?(:force_encoding)
754 768 str_fr.force_encoding('UTF-8')
755 769 end
756 770 assert_equal str_fr, l(:general_lang_name)
757 771 assert_equal ';', l(:general_csv_separator)
758 772 assert_equal ',', l(:general_csv_decimal_separator)
759 773 end
760 774 end
761 775 end
General Comments 0
You need to be logged in to leave comments. Login now