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