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