##// END OF EJS Templates
Fixed that some error messages were not displayed (#2866)....
Jean-Philippe Lang -
r2474:38db62f1e711
parent child
Show More
@@ -1,498 +1,496
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 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 IssuesController < ApplicationController
19 19 menu_item :new_issue, :only => :new
20 20
21 21 before_filter :find_issue, :only => [:show, :edit, :reply]
22 22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 23 before_filter :find_project, :only => [:new, :update_form, :preview]
24 24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
25 25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 26 accept_key_auth :index, :changes
27 27
28 28 helper :journals
29 29 helper :projects
30 30 include ProjectsHelper
31 31 helper :custom_fields
32 32 include CustomFieldsHelper
33 33 helper :issue_relations
34 34 include IssueRelationsHelper
35 35 helper :watchers
36 36 include WatchersHelper
37 37 helper :attachments
38 38 include AttachmentsHelper
39 39 helper :queries
40 40 helper :sort
41 41 include SortHelper
42 42 include IssuesHelper
43 43 helper :timelog
44 44 include Redmine::Export::PDF
45 45
46 46 def index
47 47 retrieve_query
48 48 sort_init 'id', 'desc'
49 49 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
50 50
51 51 if @query.valid?
52 52 limit = per_page_option
53 53 respond_to do |format|
54 54 format.html { }
55 55 format.atom { }
56 56 format.csv { limit = Setting.issues_export_limit.to_i }
57 57 format.pdf { limit = Setting.issues_export_limit.to_i }
58 58 end
59 59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
60 60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
61 61 @issues = Issue.find :all, :order => sort_clause,
62 62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
63 63 :conditions => @query.statement,
64 64 :limit => limit,
65 65 :offset => @issue_pages.current.offset
66 66 respond_to do |format|
67 67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
68 68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
69 69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
70 70 format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
71 71 end
72 72 else
73 73 # Send html if the query is not valid
74 74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
75 75 end
76 76 rescue ActiveRecord::RecordNotFound
77 77 render_404
78 78 end
79 79
80 80 def changes
81 81 retrieve_query
82 82 sort_init 'id', 'desc'
83 83 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
84 84
85 85 if @query.valid?
86 86 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
87 87 :conditions => @query.statement,
88 88 :limit => 25,
89 89 :order => "#{Journal.table_name}.created_on DESC"
90 90 end
91 91 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
92 92 render :layout => false, :content_type => 'application/atom+xml'
93 93 rescue ActiveRecord::RecordNotFound
94 94 render_404
95 95 end
96 96
97 97 def show
98 98 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
99 99 @journals.each_with_index {|j,i| j.indice = i+1}
100 100 @journals.reverse! if User.current.wants_comments_in_reverse_order?
101 101 @changesets = @issue.changesets
102 102 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
103 103 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
104 104 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
105 105 @priorities = Enumeration.priorities
106 106 @time_entry = TimeEntry.new
107 107 respond_to do |format|
108 108 format.html { render :template => 'issues/show.rhtml' }
109 109 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
110 110 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
111 111 end
112 112 end
113 113
114 114 # Add a new issue
115 115 # The new issue will be created from an existing one if copy_from parameter is given
116 116 def new
117 117 @issue = Issue.new
118 118 @issue.copy_from(params[:copy_from]) if params[:copy_from]
119 119 @issue.project = @project
120 120 # Tracker must be set before custom field values
121 121 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
122 122 if @issue.tracker.nil?
123 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
124 render :nothing => true, :layout => true
123 render_error 'No tracker is associated to this project. Please check the Project settings.'
125 124 return
126 125 end
127 126 if params[:issue].is_a?(Hash)
128 127 @issue.attributes = params[:issue]
129 128 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
130 129 end
131 130 @issue.author = User.current
132 131
133 132 default_status = IssueStatus.default
134 133 unless default_status
135 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
136 render :nothing => true, :layout => true
134 render_error 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
137 135 return
138 136 end
139 137 @issue.status = default_status
140 138 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
141 139
142 140 if request.get? || request.xhr?
143 141 @issue.start_date ||= Date.today
144 142 else
145 143 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
146 144 # Check that the user is allowed to apply the requested status
147 145 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
148 146 if @issue.save
149 147 attach_files(@issue, params[:attachments])
150 148 flash[:notice] = l(:notice_successful_create)
151 149 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
152 150 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
153 151 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
154 152 { :action => 'show', :id => @issue })
155 153 return
156 154 end
157 155 end
158 156 @priorities = Enumeration.priorities
159 157 render :layout => !request.xhr?
160 158 end
161 159
162 160 # Attributes that can be updated on workflow transition (without :edit permission)
163 161 # TODO: make it configurable (at least per role)
164 162 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
165 163
166 164 def edit
167 165 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
168 166 @priorities = Enumeration.priorities
169 167 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
170 168 @time_entry = TimeEntry.new
171 169
172 170 @notes = params[:notes]
173 171 journal = @issue.init_journal(User.current, @notes)
174 172 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
175 173 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
176 174 attrs = params[:issue].dup
177 175 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
178 176 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
179 177 @issue.attributes = attrs
180 178 end
181 179
182 180 if request.post?
183 181 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
184 182 @time_entry.attributes = params[:time_entry]
185 183 attachments = attach_files(@issue, params[:attachments])
186 184 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
187 185
188 186 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
189 187
190 188 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
191 189 # Log spend time
192 190 if User.current.allowed_to?(:log_time, @project)
193 191 @time_entry.save
194 192 end
195 193 if !journal.new_record?
196 194 # Only send notification if something was actually changed
197 195 flash[:notice] = l(:notice_successful_update)
198 196 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
199 197 end
200 198 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
201 199 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
202 200 end
203 201 end
204 202 rescue ActiveRecord::StaleObjectError
205 203 # Optimistic locking exception
206 204 flash.now[:error] = l(:notice_locking_conflict)
207 205 end
208 206
209 207 def reply
210 208 journal = Journal.find(params[:journal_id]) if params[:journal_id]
211 209 if journal
212 210 user = journal.user
213 211 text = journal.notes
214 212 else
215 213 user = @issue.author
216 214 text = @issue.description
217 215 end
218 216 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
219 217 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
220 218 render(:update) { |page|
221 219 page.<< "$('notes').value = \"#{content}\";"
222 220 page.show 'update'
223 221 page << "Form.Element.focus('notes');"
224 222 page << "Element.scrollTo('update');"
225 223 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
226 224 }
227 225 end
228 226
229 227 # Bulk edit a set of issues
230 228 def bulk_edit
231 229 if request.post?
232 230 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
233 231 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
234 232 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
235 233 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
236 234 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
237 235 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
238 236
239 237 unsaved_issue_ids = []
240 238 @issues.each do |issue|
241 239 journal = issue.init_journal(User.current, params[:notes])
242 240 issue.priority = priority if priority
243 241 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
244 242 issue.category = category if category || params[:category_id] == 'none'
245 243 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
246 244 issue.start_date = params[:start_date] unless params[:start_date].blank?
247 245 issue.due_date = params[:due_date] unless params[:due_date].blank?
248 246 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
249 247 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
250 248 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
251 249 # Don't save any change to the issue if the user is not authorized to apply the requested status
252 250 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
253 251 # Send notification for each issue (if changed)
254 252 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
255 253 else
256 254 # Keep unsaved issue ids to display them in flash error
257 255 unsaved_issue_ids << issue.id
258 256 end
259 257 end
260 258 if unsaved_issue_ids.empty?
261 259 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
262 260 else
263 261 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
264 262 :total => @issues.size,
265 263 :ids => '#' + unsaved_issue_ids.join(', #'))
266 264 end
267 265 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
268 266 return
269 267 end
270 268 # Find potential statuses the user could be allowed to switch issues to
271 269 @available_statuses = Workflow.find(:all, :include => :new_status,
272 270 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
273 271 @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
274 272 end
275 273
276 274 def move
277 275 @allowed_projects = []
278 276 # find projects to which the user is allowed to move the issue
279 277 if User.current.admin?
280 278 # admin is allowed to move issues to any active (visible) project
281 279 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
282 280 else
283 281 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
284 282 end
285 283 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
286 284 @target_project ||= @project
287 285 @trackers = @target_project.trackers
288 286 if request.post?
289 287 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
290 288 unsaved_issue_ids = []
291 289 @issues.each do |issue|
292 290 issue.init_journal(User.current)
293 291 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker, params[:copy_options])
294 292 end
295 293 if unsaved_issue_ids.empty?
296 294 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
297 295 else
298 296 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
299 297 :total => @issues.size,
300 298 :ids => '#' + unsaved_issue_ids.join(', #'))
301 299 end
302 300 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
303 301 return
304 302 end
305 303 render :layout => false if request.xhr?
306 304 end
307 305
308 306 def destroy
309 307 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
310 308 if @hours > 0
311 309 case params[:todo]
312 310 when 'destroy'
313 311 # nothing to do
314 312 when 'nullify'
315 313 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
316 314 when 'reassign'
317 315 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
318 316 if reassign_to.nil?
319 317 flash.now[:error] = l(:error_issue_not_found_in_project)
320 318 return
321 319 else
322 320 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
323 321 end
324 322 else
325 323 # display the destroy form
326 324 return
327 325 end
328 326 end
329 327 @issues.each(&:destroy)
330 328 redirect_to :action => 'index', :project_id => @project
331 329 end
332 330
333 331 def gantt
334 332 @gantt = Redmine::Helpers::Gantt.new(params)
335 333 retrieve_query
336 334 if @query.valid?
337 335 events = []
338 336 # Issues that have start and due dates
339 337 events += Issue.find(:all,
340 338 :order => "start_date, due_date",
341 339 :include => [:tracker, :status, :assigned_to, :priority, :project],
342 340 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
343 341 )
344 342 # Issues that don't have a due date but that are assigned to a version with a date
345 343 events += Issue.find(:all,
346 344 :order => "start_date, effective_date",
347 345 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
348 346 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
349 347 )
350 348 # Versions
351 349 events += Version.find(:all, :include => :project,
352 350 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
353 351
354 352 @gantt.events = events
355 353 end
356 354
357 355 respond_to do |format|
358 356 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
359 357 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.png") } if @gantt.respond_to?('to_image')
360 358 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
361 359 end
362 360 end
363 361
364 362 def calendar
365 363 if params[:year] and params[:year].to_i > 1900
366 364 @year = params[:year].to_i
367 365 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
368 366 @month = params[:month].to_i
369 367 end
370 368 end
371 369 @year ||= Date.today.year
372 370 @month ||= Date.today.month
373 371
374 372 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
375 373 retrieve_query
376 374 if @query.valid?
377 375 events = []
378 376 events += Issue.find(:all,
379 377 :include => [:tracker, :status, :assigned_to, :priority, :project],
380 378 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
381 379 )
382 380 events += Version.find(:all, :include => :project,
383 381 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
384 382
385 383 @calendar.events = events
386 384 end
387 385
388 386 render :layout => false if request.xhr?
389 387 end
390 388
391 389 def context_menu
392 390 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
393 391 if (@issues.size == 1)
394 392 @issue = @issues.first
395 393 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
396 394 end
397 395 projects = @issues.collect(&:project).compact.uniq
398 396 @project = projects.first if projects.size == 1
399 397
400 398 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
401 399 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
402 400 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
403 401 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
404 402 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
405 403 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
406 404 }
407 405 if @project
408 406 @assignables = @project.assignable_users
409 407 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
410 408 end
411 409
412 410 @priorities = Enumeration.priorities.reverse
413 411 @statuses = IssueStatus.find(:all, :order => 'position')
414 412 @back = request.env['HTTP_REFERER']
415 413
416 414 render :layout => false
417 415 end
418 416
419 417 def update_form
420 418 @issue = Issue.new(params[:issue])
421 419 render :action => :new, :layout => false
422 420 end
423 421
424 422 def preview
425 423 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
426 424 @attachements = @issue.attachments if @issue
427 425 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
428 426 render :partial => 'common/preview'
429 427 end
430 428
431 429 private
432 430 def find_issue
433 431 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
434 432 @project = @issue.project
435 433 rescue ActiveRecord::RecordNotFound
436 434 render_404
437 435 end
438 436
439 437 # Filter for bulk operations
440 438 def find_issues
441 439 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
442 440 raise ActiveRecord::RecordNotFound if @issues.empty?
443 441 projects = @issues.collect(&:project).compact.uniq
444 442 if projects.size == 1
445 443 @project = projects.first
446 444 else
447 445 # TODO: let users bulk edit/move/destroy issues from different projects
448 446 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
449 447 end
450 448 rescue ActiveRecord::RecordNotFound
451 449 render_404
452 450 end
453 451
454 452 def find_project
455 453 @project = Project.find(params[:project_id])
456 454 rescue ActiveRecord::RecordNotFound
457 455 render_404
458 456 end
459 457
460 458 def find_optional_project
461 459 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
462 460 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
463 461 allowed ? true : deny_access
464 462 rescue ActiveRecord::RecordNotFound
465 463 render_404
466 464 end
467 465
468 466 # Retrieve query from session or build a new query
469 467 def retrieve_query
470 468 if !params[:query_id].blank?
471 469 cond = "project_id IS NULL"
472 470 cond << " OR project_id = #{@project.id}" if @project
473 471 @query = Query.find(params[:query_id], :conditions => cond)
474 472 @query.project = @project
475 473 session[:query] = {:id => @query.id, :project_id => @query.project_id}
476 474 else
477 475 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
478 476 # Give it a name, required to be valid
479 477 @query = Query.new(:name => "_")
480 478 @query.project = @project
481 479 if params[:fields] and params[:fields].is_a? Array
482 480 params[:fields].each do |field|
483 481 @query.add_filter(field, params[:operators][field], params[:values][field])
484 482 end
485 483 else
486 484 @query.available_filters.keys.each do |field|
487 485 @query.add_short_filter(field, params[field]) if params[field]
488 486 end
489 487 end
490 488 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
491 489 else
492 490 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
493 491 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
494 492 @query.project = @project
495 493 end
496 494 end
497 495 end
498 496 end
@@ -1,963 +1,985
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 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 require File.dirname(__FILE__) + '/../test_helper'
19 19 require 'issues_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class IssuesController; def rescue_action(e) raise e end; end
23 23
24 24 class IssuesControllerTest < Test::Unit::TestCase
25 25 fixtures :projects,
26 26 :users,
27 27 :roles,
28 28 :members,
29 29 :issues,
30 30 :issue_statuses,
31 31 :versions,
32 32 :trackers,
33 33 :projects_trackers,
34 34 :issue_categories,
35 35 :enabled_modules,
36 36 :enumerations,
37 37 :attachments,
38 38 :workflows,
39 39 :custom_fields,
40 40 :custom_values,
41 41 :custom_fields_trackers,
42 42 :time_entries,
43 43 :journals,
44 44 :journal_details
45 45
46 46 def setup
47 47 @controller = IssuesController.new
48 48 @request = ActionController::TestRequest.new
49 49 @response = ActionController::TestResponse.new
50 50 User.current = nil
51 51 end
52 52
53 53 def test_index_routing
54 54 assert_routing(
55 55 {:method => :get, :path => '/issues'},
56 56 :controller => 'issues', :action => 'index'
57 57 )
58 58 end
59 59
60 60 def test_index
61 61 get :index
62 62 assert_response :success
63 63 assert_template 'index.rhtml'
64 64 assert_not_nil assigns(:issues)
65 65 assert_nil assigns(:project)
66 66 assert_tag :tag => 'a', :content => /Can't print recipes/
67 67 assert_tag :tag => 'a', :content => /Subproject issue/
68 68 # private projects hidden
69 69 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
70 70 assert_no_tag :tag => 'a', :content => /Issue on project 2/
71 71 end
72 72
73 73 def test_index_should_not_list_issues_when_module_disabled
74 74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 75 get :index
76 76 assert_response :success
77 77 assert_template 'index.rhtml'
78 78 assert_not_nil assigns(:issues)
79 79 assert_nil assigns(:project)
80 80 assert_no_tag :tag => 'a', :content => /Can't print recipes/
81 81 assert_tag :tag => 'a', :content => /Subproject issue/
82 82 end
83 83
84 84 def test_index_with_project_routing
85 85 assert_routing(
86 86 {:method => :get, :path => '/projects/23/issues'},
87 87 :controller => 'issues', :action => 'index', :project_id => '23'
88 88 )
89 89 end
90 90
91 91 def test_index_should_not_list_issues_when_module_disabled
92 92 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
93 93 get :index
94 94 assert_response :success
95 95 assert_template 'index.rhtml'
96 96 assert_not_nil assigns(:issues)
97 97 assert_nil assigns(:project)
98 98 assert_no_tag :tag => 'a', :content => /Can't print recipes/
99 99 assert_tag :tag => 'a', :content => /Subproject issue/
100 100 end
101 101
102 102 def test_index_with_project_routing
103 103 assert_routing(
104 104 {:method => :get, :path => 'projects/23/issues'},
105 105 :controller => 'issues', :action => 'index', :project_id => '23'
106 106 )
107 107 end
108 108
109 109 def test_index_with_project
110 110 Setting.display_subprojects_issues = 0
111 111 get :index, :project_id => 1
112 112 assert_response :success
113 113 assert_template 'index.rhtml'
114 114 assert_not_nil assigns(:issues)
115 115 assert_tag :tag => 'a', :content => /Can't print recipes/
116 116 assert_no_tag :tag => 'a', :content => /Subproject issue/
117 117 end
118 118
119 119 def test_index_with_project_and_subprojects
120 120 Setting.display_subprojects_issues = 1
121 121 get :index, :project_id => 1
122 122 assert_response :success
123 123 assert_template 'index.rhtml'
124 124 assert_not_nil assigns(:issues)
125 125 assert_tag :tag => 'a', :content => /Can't print recipes/
126 126 assert_tag :tag => 'a', :content => /Subproject issue/
127 127 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
128 128 end
129 129
130 130 def test_index_with_project_and_subprojects_should_show_private_subprojects
131 131 @request.session[:user_id] = 2
132 132 Setting.display_subprojects_issues = 1
133 133 get :index, :project_id => 1
134 134 assert_response :success
135 135 assert_template 'index.rhtml'
136 136 assert_not_nil assigns(:issues)
137 137 assert_tag :tag => 'a', :content => /Can't print recipes/
138 138 assert_tag :tag => 'a', :content => /Subproject issue/
139 139 assert_tag :tag => 'a', :content => /Issue of a private subproject/
140 140 end
141 141
142 142 def test_index_with_project_routing_formatted
143 143 assert_routing(
144 144 {:method => :get, :path => 'projects/23/issues.pdf'},
145 145 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
146 146 )
147 147 assert_routing(
148 148 {:method => :get, :path => 'projects/23/issues.atom'},
149 149 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
150 150 )
151 151 end
152 152
153 153 def test_index_with_project_and_filter
154 154 get :index, :project_id => 1, :set_filter => 1
155 155 assert_response :success
156 156 assert_template 'index.rhtml'
157 157 assert_not_nil assigns(:issues)
158 158 end
159 159
160 160 def test_index_csv_with_project
161 161 get :index, :format => 'csv'
162 162 assert_response :success
163 163 assert_not_nil assigns(:issues)
164 164 assert_equal 'text/csv', @response.content_type
165 165
166 166 get :index, :project_id => 1, :format => 'csv'
167 167 assert_response :success
168 168 assert_not_nil assigns(:issues)
169 169 assert_equal 'text/csv', @response.content_type
170 170 end
171 171
172 172 def test_index_formatted
173 173 assert_routing(
174 174 {:method => :get, :path => 'issues.pdf'},
175 175 :controller => 'issues', :action => 'index', :format => 'pdf'
176 176 )
177 177 assert_routing(
178 178 {:method => :get, :path => 'issues.atom'},
179 179 :controller => 'issues', :action => 'index', :format => 'atom'
180 180 )
181 181 end
182 182
183 183 def test_index_pdf
184 184 get :index, :format => 'pdf'
185 185 assert_response :success
186 186 assert_not_nil assigns(:issues)
187 187 assert_equal 'application/pdf', @response.content_type
188 188
189 189 get :index, :project_id => 1, :format => 'pdf'
190 190 assert_response :success
191 191 assert_not_nil assigns(:issues)
192 192 assert_equal 'application/pdf', @response.content_type
193 193 end
194 194
195 195 def test_index_sort
196 196 get :index, :sort_key => 'tracker'
197 197 assert_response :success
198 198
199 199 sort_params = @request.session['issuesindex_sort']
200 200 assert sort_params.is_a?(Hash)
201 201 assert_equal 'tracker', sort_params[:key]
202 202 assert_equal 'ASC', sort_params[:order]
203 203 end
204 204
205 205 def test_gantt
206 206 get :gantt, :project_id => 1
207 207 assert_response :success
208 208 assert_template 'gantt.rhtml'
209 209 assert_not_nil assigns(:gantt)
210 210 events = assigns(:gantt).events
211 211 assert_not_nil events
212 212 # Issue with start and due dates
213 213 i = Issue.find(1)
214 214 assert_not_nil i.due_date
215 215 assert events.include?(Issue.find(1))
216 216 # Issue with without due date but targeted to a version with date
217 217 i = Issue.find(2)
218 218 assert_nil i.due_date
219 219 assert events.include?(i)
220 220 end
221 221
222 222 def test_cross_project_gantt
223 223 get :gantt
224 224 assert_response :success
225 225 assert_template 'gantt.rhtml'
226 226 assert_not_nil assigns(:gantt)
227 227 events = assigns(:gantt).events
228 228 assert_not_nil events
229 229 end
230 230
231 231 def test_gantt_export_to_pdf
232 232 get :gantt, :project_id => 1, :format => 'pdf'
233 233 assert_response :success
234 234 assert_equal 'application/pdf', @response.content_type
235 235 assert @response.body.starts_with?('%PDF')
236 236 assert_not_nil assigns(:gantt)
237 237 end
238 238
239 239 def test_cross_project_gantt_export_to_pdf
240 240 get :gantt, :format => 'pdf'
241 241 assert_response :success
242 242 assert_equal 'application/pdf', @response.content_type
243 243 assert @response.body.starts_with?('%PDF')
244 244 assert_not_nil assigns(:gantt)
245 245 end
246 246
247 247 if Object.const_defined?(:Magick)
248 248 def test_gantt_image
249 249 get :gantt, :project_id => 1, :format => 'png'
250 250 assert_response :success
251 251 assert_equal 'image/png', @response.content_type
252 252 end
253 253 else
254 254 puts "RMagick not installed. Skipping tests !!!"
255 255 end
256 256
257 257 def test_calendar
258 258 get :calendar, :project_id => 1
259 259 assert_response :success
260 260 assert_template 'calendar'
261 261 assert_not_nil assigns(:calendar)
262 262 end
263 263
264 264 def test_cross_project_calendar
265 265 get :calendar
266 266 assert_response :success
267 267 assert_template 'calendar'
268 268 assert_not_nil assigns(:calendar)
269 269 end
270 270
271 271 def test_changes
272 272 get :changes, :project_id => 1
273 273 assert_response :success
274 274 assert_not_nil assigns(:journals)
275 275 assert_equal 'application/atom+xml', @response.content_type
276 276 end
277 277
278 278 def test_show_routing
279 279 assert_routing(
280 280 {:method => :get, :path => '/issues/64'},
281 281 :controller => 'issues', :action => 'show', :id => '64'
282 282 )
283 283 end
284 284
285 285 def test_show_routing_formatted
286 286 assert_routing(
287 287 {:method => :get, :path => '/issues/2332.pdf'},
288 288 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
289 289 )
290 290 assert_routing(
291 291 {:method => :get, :path => '/issues/23123.atom'},
292 292 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
293 293 )
294 294 end
295 295
296 296 def test_show_by_anonymous
297 297 get :show, :id => 1
298 298 assert_response :success
299 299 assert_template 'show.rhtml'
300 300 assert_not_nil assigns(:issue)
301 301 assert_equal Issue.find(1), assigns(:issue)
302 302
303 303 # anonymous role is allowed to add a note
304 304 assert_tag :tag => 'form',
305 305 :descendant => { :tag => 'fieldset',
306 306 :child => { :tag => 'legend',
307 307 :content => /Notes/ } }
308 308 end
309 309
310 310 def test_show_by_manager
311 311 @request.session[:user_id] = 2
312 312 get :show, :id => 1
313 313 assert_response :success
314 314
315 315 assert_tag :tag => 'form',
316 316 :descendant => { :tag => 'fieldset',
317 317 :child => { :tag => 'legend',
318 318 :content => /Change properties/ } },
319 319 :descendant => { :tag => 'fieldset',
320 320 :child => { :tag => 'legend',
321 321 :content => /Log time/ } },
322 322 :descendant => { :tag => 'fieldset',
323 323 :child => { :tag => 'legend',
324 324 :content => /Notes/ } }
325 325 end
326 326
327 327 def test_show_should_not_disclose_relations_to_invisible_issues
328 328 Setting.cross_project_issue_relations = '1'
329 329 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
330 330 # Relation to a private project issue
331 331 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
332 332
333 333 get :show, :id => 1
334 334 assert_response :success
335 335
336 336 assert_tag :div, :attributes => { :id => 'relations' },
337 337 :descendant => { :tag => 'a', :content => /#2$/ }
338 338 assert_no_tag :div, :attributes => { :id => 'relations' },
339 339 :descendant => { :tag => 'a', :content => /#4$/ }
340 340 end
341 341
342 342 def test_new_routing
343 343 assert_routing(
344 344 {:method => :get, :path => '/projects/1/issues/new'},
345 345 :controller => 'issues', :action => 'new', :project_id => '1'
346 346 )
347 347 assert_recognizes(
348 348 {:controller => 'issues', :action => 'new', :project_id => '1'},
349 349 {:method => :post, :path => '/projects/1/issues'}
350 350 )
351 351 end
352 352
353 353 def test_show_export_to_pdf
354 354 get :show, :id => 3, :format => 'pdf'
355 355 assert_response :success
356 356 assert_equal 'application/pdf', @response.content_type
357 357 assert @response.body.starts_with?('%PDF')
358 358 assert_not_nil assigns(:issue)
359 359 end
360 360
361 361 def test_get_new
362 362 @request.session[:user_id] = 2
363 363 get :new, :project_id => 1, :tracker_id => 1
364 364 assert_response :success
365 365 assert_template 'new'
366 366
367 367 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
368 368 :value => 'Default string' }
369 369 end
370 370
371 371 def test_get_new_without_tracker_id
372 372 @request.session[:user_id] = 2
373 373 get :new, :project_id => 1
374 374 assert_response :success
375 375 assert_template 'new'
376 376
377 377 issue = assigns(:issue)
378 378 assert_not_nil issue
379 379 assert_equal Project.find(1).trackers.first, issue.tracker
380 380 end
381 381
382 def test_get_new_with_no_default_status_should_display_an_error
383 @request.session[:user_id] = 2
384 IssueStatus.delete_all
385
386 get :new, :project_id => 1
387 assert_response 500
388 assert_not_nil flash[:error]
389 assert_tag :tag => 'div', :attributes => { :class => /error/ },
390 :content => /No default issue/
391 end
392
393 def test_get_new_with_no_tracker_should_display_an_error
394 @request.session[:user_id] = 2
395 Tracker.delete_all
396
397 get :new, :project_id => 1
398 assert_response 500
399 assert_not_nil flash[:error]
400 assert_tag :tag => 'div', :attributes => { :class => /error/ },
401 :content => /No tracker/
402 end
403
382 404 def test_update_new_form
383 405 @request.session[:user_id] = 2
384 406 xhr :post, :new, :project_id => 1,
385 407 :issue => {:tracker_id => 2,
386 408 :subject => 'This is the test_new issue',
387 409 :description => 'This is the description',
388 410 :priority_id => 5}
389 411 assert_response :success
390 412 assert_template 'new'
391 413 end
392 414
393 415 def test_post_new
394 416 @request.session[:user_id] = 2
395 417 post :new, :project_id => 1,
396 418 :issue => {:tracker_id => 3,
397 419 :subject => 'This is the test_new issue',
398 420 :description => 'This is the description',
399 421 :priority_id => 5,
400 422 :estimated_hours => '',
401 423 :custom_field_values => {'2' => 'Value for field 2'}}
402 424 assert_redirected_to :action => 'show'
403 425
404 426 issue = Issue.find_by_subject('This is the test_new issue')
405 427 assert_not_nil issue
406 428 assert_equal 2, issue.author_id
407 429 assert_equal 3, issue.tracker_id
408 430 assert_nil issue.estimated_hours
409 431 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
410 432 assert_not_nil v
411 433 assert_equal 'Value for field 2', v.value
412 434 end
413 435
414 436 def test_post_new_and_continue
415 437 @request.session[:user_id] = 2
416 438 post :new, :project_id => 1,
417 439 :issue => {:tracker_id => 3,
418 440 :subject => 'This is first issue',
419 441 :priority_id => 5},
420 442 :continue => ''
421 443 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
422 444 end
423 445
424 446 def test_post_new_without_custom_fields_param
425 447 @request.session[:user_id] = 2
426 448 post :new, :project_id => 1,
427 449 :issue => {:tracker_id => 1,
428 450 :subject => 'This is the test_new issue',
429 451 :description => 'This is the description',
430 452 :priority_id => 5}
431 453 assert_redirected_to :action => 'show'
432 454 end
433 455
434 456 def test_post_new_with_required_custom_field_and_without_custom_fields_param
435 457 field = IssueCustomField.find_by_name('Database')
436 458 field.update_attribute(:is_required, true)
437 459
438 460 @request.session[:user_id] = 2
439 461 post :new, :project_id => 1,
440 462 :issue => {:tracker_id => 1,
441 463 :subject => 'This is the test_new issue',
442 464 :description => 'This is the description',
443 465 :priority_id => 5}
444 466 assert_response :success
445 467 assert_template 'new'
446 468 issue = assigns(:issue)
447 469 assert_not_nil issue
448 470 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
449 471 end
450 472
451 473 def test_post_new_with_watchers
452 474 @request.session[:user_id] = 2
453 475 ActionMailer::Base.deliveries.clear
454 476
455 477 assert_difference 'Watcher.count', 2 do
456 478 post :new, :project_id => 1,
457 479 :issue => {:tracker_id => 1,
458 480 :subject => 'This is a new issue with watchers',
459 481 :description => 'This is the description',
460 482 :priority_id => 5,
461 483 :watcher_user_ids => ['2', '3']}
462 484 end
463 485 issue = Issue.find_by_subject('This is a new issue with watchers')
464 486 assert_not_nil issue
465 487 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
466 488
467 489 # Watchers added
468 490 assert_equal [2, 3], issue.watcher_user_ids.sort
469 491 assert issue.watched_by?(User.find(3))
470 492 # Watchers notified
471 493 mail = ActionMailer::Base.deliveries.last
472 494 assert_kind_of TMail::Mail, mail
473 495 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
474 496 end
475 497
476 498 def test_post_should_preserve_fields_values_on_validation_failure
477 499 @request.session[:user_id] = 2
478 500 post :new, :project_id => 1,
479 501 :issue => {:tracker_id => 1,
480 502 # empty subject
481 503 :subject => '',
482 504 :description => 'This is a description',
483 505 :priority_id => 6,
484 506 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
485 507 assert_response :success
486 508 assert_template 'new'
487 509
488 510 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
489 511 :content => 'This is a description'
490 512 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
491 513 :child => { :tag => 'option', :attributes => { :selected => 'selected',
492 514 :value => '6' },
493 515 :content => 'High' }
494 516 # Custom fields
495 517 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
496 518 :child => { :tag => 'option', :attributes => { :selected => 'selected',
497 519 :value => 'Oracle' },
498 520 :content => 'Oracle' }
499 521 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
500 522 :value => 'Value for field 2'}
501 523 end
502 524
503 525 def test_copy_routing
504 526 assert_routing(
505 527 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
506 528 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
507 529 )
508 530 end
509 531
510 532 def test_copy_issue
511 533 @request.session[:user_id] = 2
512 534 get :new, :project_id => 1, :copy_from => 1
513 535 assert_template 'new'
514 536 assert_not_nil assigns(:issue)
515 537 orig = Issue.find(1)
516 538 assert_equal orig.subject, assigns(:issue).subject
517 539 end
518 540
519 541 def test_edit_routing
520 542 assert_routing(
521 543 {:method => :get, :path => '/issues/1/edit'},
522 544 :controller => 'issues', :action => 'edit', :id => '1'
523 545 )
524 546 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
525 547 {:controller => 'issues', :action => 'edit', :id => '1'},
526 548 {:method => :post, :path => '/issues/1/edit'}
527 549 )
528 550 end
529 551
530 552 def test_get_edit
531 553 @request.session[:user_id] = 2
532 554 get :edit, :id => 1
533 555 assert_response :success
534 556 assert_template 'edit'
535 557 assert_not_nil assigns(:issue)
536 558 assert_equal Issue.find(1), assigns(:issue)
537 559 end
538 560
539 561 def test_get_edit_with_params
540 562 @request.session[:user_id] = 2
541 563 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
542 564 assert_response :success
543 565 assert_template 'edit'
544 566
545 567 issue = assigns(:issue)
546 568 assert_not_nil issue
547 569
548 570 assert_equal 5, issue.status_id
549 571 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
550 572 :child => { :tag => 'option',
551 573 :content => 'Closed',
552 574 :attributes => { :selected => 'selected' } }
553 575
554 576 assert_equal 7, issue.priority_id
555 577 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
556 578 :child => { :tag => 'option',
557 579 :content => 'Urgent',
558 580 :attributes => { :selected => 'selected' } }
559 581 end
560 582
561 583 def test_reply_routing
562 584 assert_routing(
563 585 {:method => :post, :path => '/issues/1/quoted'},
564 586 :controller => 'issues', :action => 'reply', :id => '1'
565 587 )
566 588 end
567 589
568 590 def test_reply_to_issue
569 591 @request.session[:user_id] = 2
570 592 get :reply, :id => 1
571 593 assert_response :success
572 594 assert_select_rjs :show, "update"
573 595 end
574 596
575 597 def test_reply_to_note
576 598 @request.session[:user_id] = 2
577 599 get :reply, :id => 1, :journal_id => 2
578 600 assert_response :success
579 601 assert_select_rjs :show, "update"
580 602 end
581 603
582 604 def test_post_edit_without_custom_fields_param
583 605 @request.session[:user_id] = 2
584 606 ActionMailer::Base.deliveries.clear
585 607
586 608 issue = Issue.find(1)
587 609 assert_equal '125', issue.custom_value_for(2).value
588 610 old_subject = issue.subject
589 611 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
590 612
591 613 assert_difference('Journal.count') do
592 614 assert_difference('JournalDetail.count', 2) do
593 615 post :edit, :id => 1, :issue => {:subject => new_subject,
594 616 :priority_id => '6',
595 617 :category_id => '1' # no change
596 618 }
597 619 end
598 620 end
599 621 assert_redirected_to :action => 'show', :id => '1'
600 622 issue.reload
601 623 assert_equal new_subject, issue.subject
602 624 # Make sure custom fields were not cleared
603 625 assert_equal '125', issue.custom_value_for(2).value
604 626
605 627 mail = ActionMailer::Base.deliveries.last
606 628 assert_kind_of TMail::Mail, mail
607 629 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
608 630 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
609 631 end
610 632
611 633 def test_post_edit_with_custom_field_change
612 634 @request.session[:user_id] = 2
613 635 issue = Issue.find(1)
614 636 assert_equal '125', issue.custom_value_for(2).value
615 637
616 638 assert_difference('Journal.count') do
617 639 assert_difference('JournalDetail.count', 3) do
618 640 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
619 641 :priority_id => '6',
620 642 :category_id => '1', # no change
621 643 :custom_field_values => { '2' => 'New custom value' }
622 644 }
623 645 end
624 646 end
625 647 assert_redirected_to :action => 'show', :id => '1'
626 648 issue.reload
627 649 assert_equal 'New custom value', issue.custom_value_for(2).value
628 650
629 651 mail = ActionMailer::Base.deliveries.last
630 652 assert_kind_of TMail::Mail, mail
631 653 assert mail.body.include?("Searchable field changed from 125 to New custom value")
632 654 end
633 655
634 656 def test_post_edit_with_status_and_assignee_change
635 657 issue = Issue.find(1)
636 658 assert_equal 1, issue.status_id
637 659 @request.session[:user_id] = 2
638 660 assert_difference('TimeEntry.count', 0) do
639 661 post :edit,
640 662 :id => 1,
641 663 :issue => { :status_id => 2, :assigned_to_id => 3 },
642 664 :notes => 'Assigned to dlopper',
643 665 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.activities.first }
644 666 end
645 667 assert_redirected_to :action => 'show', :id => '1'
646 668 issue.reload
647 669 assert_equal 2, issue.status_id
648 670 j = issue.journals.find(:first, :order => 'id DESC')
649 671 assert_equal 'Assigned to dlopper', j.notes
650 672 assert_equal 2, j.details.size
651 673
652 674 mail = ActionMailer::Base.deliveries.last
653 675 assert mail.body.include?("Status changed from New to Assigned")
654 676 end
655 677
656 678 def test_post_edit_with_note_only
657 679 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
658 680 # anonymous user
659 681 post :edit,
660 682 :id => 1,
661 683 :notes => notes
662 684 assert_redirected_to :action => 'show', :id => '1'
663 685 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
664 686 assert_equal notes, j.notes
665 687 assert_equal 0, j.details.size
666 688 assert_equal User.anonymous, j.user
667 689
668 690 mail = ActionMailer::Base.deliveries.last
669 691 assert mail.body.include?(notes)
670 692 end
671 693
672 694 def test_post_edit_with_note_and_spent_time
673 695 @request.session[:user_id] = 2
674 696 spent_hours_before = Issue.find(1).spent_hours
675 697 assert_difference('TimeEntry.count') do
676 698 post :edit,
677 699 :id => 1,
678 700 :notes => '2.5 hours added',
679 701 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.activities.first }
680 702 end
681 703 assert_redirected_to :action => 'show', :id => '1'
682 704
683 705 issue = Issue.find(1)
684 706
685 707 j = issue.journals.find(:first, :order => 'id DESC')
686 708 assert_equal '2.5 hours added', j.notes
687 709 assert_equal 0, j.details.size
688 710
689 711 t = issue.time_entries.find(:first, :order => 'id DESC')
690 712 assert_not_nil t
691 713 assert_equal 2.5, t.hours
692 714 assert_equal spent_hours_before + 2.5, issue.spent_hours
693 715 end
694 716
695 717 def test_post_edit_with_attachment_only
696 718 set_tmp_attachments_directory
697 719
698 720 # Delete all fixtured journals, a race condition can occur causing the wrong
699 721 # journal to get fetched in the next find.
700 722 Journal.delete_all
701 723
702 724 # anonymous user
703 725 post :edit,
704 726 :id => 1,
705 727 :notes => '',
706 728 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
707 729 assert_redirected_to :action => 'show', :id => '1'
708 730 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
709 731 assert j.notes.blank?
710 732 assert_equal 1, j.details.size
711 733 assert_equal 'testfile.txt', j.details.first.value
712 734 assert_equal User.anonymous, j.user
713 735
714 736 mail = ActionMailer::Base.deliveries.last
715 737 assert mail.body.include?('testfile.txt')
716 738 end
717 739
718 740 def test_post_edit_with_no_change
719 741 issue = Issue.find(1)
720 742 issue.journals.clear
721 743 ActionMailer::Base.deliveries.clear
722 744
723 745 post :edit,
724 746 :id => 1,
725 747 :notes => ''
726 748 assert_redirected_to :action => 'show', :id => '1'
727 749
728 750 issue.reload
729 751 assert issue.journals.empty?
730 752 # No email should be sent
731 753 assert ActionMailer::Base.deliveries.empty?
732 754 end
733 755
734 756 def test_post_edit_with_invalid_spent_time
735 757 @request.session[:user_id] = 2
736 758 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
737 759
738 760 assert_no_difference('Journal.count') do
739 761 post :edit,
740 762 :id => 1,
741 763 :notes => notes,
742 764 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
743 765 end
744 766 assert_response :success
745 767 assert_template 'edit'
746 768
747 769 assert_tag :textarea, :attributes => { :name => 'notes' },
748 770 :content => notes
749 771 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
750 772 end
751 773
752 774 def test_bulk_edit
753 775 @request.session[:user_id] = 2
754 776 # update issues priority
755 777 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
756 778 :assigned_to_id => '',
757 779 :custom_field_values => {'2' => ''},
758 780 :notes => 'Bulk editing'
759 781 assert_response 302
760 782 # check that the issues were updated
761 783 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
762 784
763 785 issue = Issue.find(1)
764 786 journal = issue.journals.find(:first, :order => 'created_on DESC')
765 787 assert_equal '125', issue.custom_value_for(2).value
766 788 assert_equal 'Bulk editing', journal.notes
767 789 assert_equal 1, journal.details.size
768 790 end
769 791
770 792 def test_bulk_edit_custom_field
771 793 @request.session[:user_id] = 2
772 794 # update issues priority
773 795 post :bulk_edit, :ids => [1, 2], :priority_id => '',
774 796 :assigned_to_id => '',
775 797 :custom_field_values => {'2' => '777'},
776 798 :notes => 'Bulk editing custom field'
777 799 assert_response 302
778 800
779 801 issue = Issue.find(1)
780 802 journal = issue.journals.find(:first, :order => 'created_on DESC')
781 803 assert_equal '777', issue.custom_value_for(2).value
782 804 assert_equal 1, journal.details.size
783 805 assert_equal '125', journal.details.first.old_value
784 806 assert_equal '777', journal.details.first.value
785 807 end
786 808
787 809 def test_bulk_unassign
788 810 assert_not_nil Issue.find(2).assigned_to
789 811 @request.session[:user_id] = 2
790 812 # unassign issues
791 813 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
792 814 assert_response 302
793 815 # check that the issues were updated
794 816 assert_nil Issue.find(2).assigned_to
795 817 end
796 818
797 819 def test_move_routing
798 820 assert_routing(
799 821 {:method => :get, :path => '/issues/1/move'},
800 822 :controller => 'issues', :action => 'move', :id => '1'
801 823 )
802 824 assert_recognizes(
803 825 {:controller => 'issues', :action => 'move', :id => '1'},
804 826 {:method => :post, :path => '/issues/1/move'}
805 827 )
806 828 end
807 829
808 830 def test_move_one_issue_to_another_project
809 831 @request.session[:user_id] = 1
810 832 post :move, :id => 1, :new_project_id => 2
811 833 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
812 834 assert_equal 2, Issue.find(1).project_id
813 835 end
814 836
815 837 def test_bulk_move_to_another_project
816 838 @request.session[:user_id] = 1
817 839 post :move, :ids => [1, 2], :new_project_id => 2
818 840 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
819 841 # Issues moved to project 2
820 842 assert_equal 2, Issue.find(1).project_id
821 843 assert_equal 2, Issue.find(2).project_id
822 844 # No tracker change
823 845 assert_equal 1, Issue.find(1).tracker_id
824 846 assert_equal 2, Issue.find(2).tracker_id
825 847 end
826 848
827 849 def test_bulk_move_to_another_tracker
828 850 @request.session[:user_id] = 1
829 851 post :move, :ids => [1, 2], :new_tracker_id => 2
830 852 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
831 853 assert_equal 2, Issue.find(1).tracker_id
832 854 assert_equal 2, Issue.find(2).tracker_id
833 855 end
834 856
835 857 def test_bulk_copy_to_another_project
836 858 @request.session[:user_id] = 1
837 859 assert_difference 'Issue.count', 2 do
838 860 assert_no_difference 'Project.find(1).issues.count' do
839 861 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
840 862 end
841 863 end
842 864 assert_redirected_to 'projects/ecookbook/issues'
843 865 end
844 866
845 867 def test_context_menu_one_issue
846 868 @request.session[:user_id] = 2
847 869 get :context_menu, :ids => [1]
848 870 assert_response :success
849 871 assert_template 'context_menu'
850 872 assert_tag :tag => 'a', :content => 'Edit',
851 873 :attributes => { :href => '/issues/1/edit',
852 874 :class => 'icon-edit' }
853 875 assert_tag :tag => 'a', :content => 'Closed',
854 876 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
855 877 :class => '' }
856 878 assert_tag :tag => 'a', :content => 'Immediate',
857 879 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
858 880 :class => '' }
859 881 assert_tag :tag => 'a', :content => 'Dave Lopper',
860 882 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
861 883 :class => '' }
862 884 assert_tag :tag => 'a', :content => 'Copy',
863 885 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
864 886 :class => 'icon-copy' }
865 887 assert_tag :tag => 'a', :content => 'Move',
866 888 :attributes => { :href => '/issues/move?ids%5B%5D=1',
867 889 :class => 'icon-move' }
868 890 assert_tag :tag => 'a', :content => 'Delete',
869 891 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
870 892 :class => 'icon-del' }
871 893 end
872 894
873 895 def test_context_menu_one_issue_by_anonymous
874 896 get :context_menu, :ids => [1]
875 897 assert_response :success
876 898 assert_template 'context_menu'
877 899 assert_tag :tag => 'a', :content => 'Delete',
878 900 :attributes => { :href => '#',
879 901 :class => 'icon-del disabled' }
880 902 end
881 903
882 904 def test_context_menu_multiple_issues_of_same_project
883 905 @request.session[:user_id] = 2
884 906 get :context_menu, :ids => [1, 2]
885 907 assert_response :success
886 908 assert_template 'context_menu'
887 909 assert_tag :tag => 'a', :content => 'Edit',
888 910 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
889 911 :class => 'icon-edit' }
890 912 assert_tag :tag => 'a', :content => 'Immediate',
891 913 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
892 914 :class => '' }
893 915 assert_tag :tag => 'a', :content => 'Dave Lopper',
894 916 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
895 917 :class => '' }
896 918 assert_tag :tag => 'a', :content => 'Move',
897 919 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
898 920 :class => 'icon-move' }
899 921 assert_tag :tag => 'a', :content => 'Delete',
900 922 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
901 923 :class => 'icon-del' }
902 924 end
903 925
904 926 def test_context_menu_multiple_issues_of_different_project
905 927 @request.session[:user_id] = 2
906 928 get :context_menu, :ids => [1, 2, 4]
907 929 assert_response :success
908 930 assert_template 'context_menu'
909 931 assert_tag :tag => 'a', :content => 'Delete',
910 932 :attributes => { :href => '#',
911 933 :class => 'icon-del disabled' }
912 934 end
913 935
914 936 def test_destroy_routing
915 937 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
916 938 {:controller => 'issues', :action => 'destroy', :id => '1'},
917 939 {:method => :post, :path => '/issues/1/destroy'}
918 940 )
919 941 end
920 942
921 943 def test_destroy_issue_with_no_time_entries
922 944 assert_nil TimeEntry.find_by_issue_id(2)
923 945 @request.session[:user_id] = 2
924 946 post :destroy, :id => 2
925 947 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
926 948 assert_nil Issue.find_by_id(2)
927 949 end
928 950
929 951 def test_destroy_issues_with_time_entries
930 952 @request.session[:user_id] = 2
931 953 post :destroy, :ids => [1, 3]
932 954 assert_response :success
933 955 assert_template 'destroy'
934 956 assert_not_nil assigns(:hours)
935 957 assert Issue.find_by_id(1) && Issue.find_by_id(3)
936 958 end
937 959
938 960 def test_destroy_issues_and_destroy_time_entries
939 961 @request.session[:user_id] = 2
940 962 post :destroy, :ids => [1, 3], :todo => 'destroy'
941 963 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
942 964 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
943 965 assert_nil TimeEntry.find_by_id([1, 2])
944 966 end
945 967
946 968 def test_destroy_issues_and_assign_time_entries_to_project
947 969 @request.session[:user_id] = 2
948 970 post :destroy, :ids => [1, 3], :todo => 'nullify'
949 971 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
950 972 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
951 973 assert_nil TimeEntry.find(1).issue_id
952 974 assert_nil TimeEntry.find(2).issue_id
953 975 end
954 976
955 977 def test_destroy_issues_and_reassign_time_entries_to_another_issue
956 978 @request.session[:user_id] = 2
957 979 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
958 980 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
959 981 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
960 982 assert_equal 2, TimeEntry.find(1).issue_id
961 983 assert_equal 2, TimeEntry.find(2).issue_id
962 984 end
963 985 end
General Comments 0
You need to be logged in to leave comments. Login now