comparison app/controllers/.svn/text-base/issues_controller.rb.svn-base @ 14:1d32c0a0efbf

* Update to SVN trunk (revisions 3892-4040)
author Chris Cannam
date Wed, 25 Aug 2010 16:30:24 +0100
parents 513646585e45
children 40f7cfd4df19
comparison
equal deleted inserted replaced
4:9cc62779c13a 14:1d32c0a0efbf
17 17
18 class IssuesController < ApplicationController 18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create] 19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues 20 default_search_scope :issues
21 21
22 before_filter :find_issue, :only => [:show, :edit, :update, :reply] 22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] 23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
24 before_filter :find_project, :only => [:new, :create, :update_form, :preview, :auto_complete] 24 before_filter :find_project, :only => [:new, :create]
25 before_filter :authorize, :except => [:index, :changes, :preview, :context_menu] 25 before_filter :authorize, :except => [:index]
26 before_filter :find_optional_project, :only => [:index, :changes] 26 before_filter :find_optional_project, :only => [:index]
27 before_filter :check_for_default_issue_status, :only => [:new, :create] 27 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create] 28 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 accept_key_auth :index, :show, :changes 29 accept_key_auth :index, :show
30 30
31 rescue_from Query::StatementInvalid, :with => :query_statement_invalid 31 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 32
33 helper :journals 33 helper :journals
34 helper :projects 34 helper :projects
52 verify :method => [:post, :delete], 52 verify :method => [:post, :delete],
53 :only => :destroy, 53 :only => :destroy,
54 :render => { :nothing => true, :status => :method_not_allowed } 54 :render => { :nothing => true, :status => :method_not_allowed }
55 55
56 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } 56 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
57 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
57 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } 58 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
58 59
59 def index 60 def index
60 retrieve_query 61 retrieve_query
61 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) 62 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
89 end 90 end
90 else 91 else
91 # Send html if the query is not valid 92 # Send html if the query is not valid
92 render(:template => 'issues/index.rhtml', :layout => !request.xhr?) 93 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
93 end 94 end
94 rescue ActiveRecord::RecordNotFound
95 render_404
96 end
97
98 def changes
99 retrieve_query
100 sort_init 'id', 'desc'
101 sort_update(@query.sortable_columns)
102
103 if @query.valid?
104 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
105 :limit => 25)
106 end
107 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
108 render :layout => false, :content_type => 'application/atom+xml'
109 rescue ActiveRecord::RecordNotFound 95 rescue ActiveRecord::RecordNotFound
110 render_404 96 render_404
111 end 97 end
112 98
113 def show 99 def show
122 @time_entry = TimeEntry.new 108 @time_entry = TimeEntry.new
123 respond_to do |format| 109 respond_to do |format|
124 format.html { render :template => 'issues/show.rhtml' } 110 format.html { render :template => 'issues/show.rhtml' }
125 format.xml { render :layout => false } 111 format.xml { render :layout => false }
126 format.json { render :text => @issue.to_json, :layout => false } 112 format.json { render :text => @issue.to_json, :layout => false }
127 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' } 113 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
128 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } 114 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
129 end 115 end
130 end 116 end
131 117
132 # Add a new issue 118 # Add a new issue
133 # The new issue will be created from an existing one if copy_from parameter is given 119 # The new issue will be created from an existing one if copy_from parameter is given
134 def new 120 def new
135 render :action => 'new', :layout => !request.xhr? 121 respond_to do |format|
122 format.html { render :action => 'new', :layout => !request.xhr? }
123 format.js { render :partial => 'attributes' }
124 end
136 end 125 end
137 126
138 def create 127 def create
139 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue }) 128 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
140 if @issue.save 129 if @issue.save
198 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false } 187 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
199 end 188 end
200 end 189 end
201 end 190 end
202 191
203 def reply
204 journal = Journal.find(params[:journal_id]) if params[:journal_id]
205 if journal
206 user = journal.user
207 text = journal.notes
208 else
209 user = @issue.author
210 text = @issue.description
211 end
212 # Replaces pre blocks with [...]
213 text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
214 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
215 content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
216
217 render(:update) { |page|
218 page.<< "$('notes').value = \"#{escape_javascript content}\";"
219 page.show 'update'
220 page << "Form.Element.focus('notes');"
221 page << "Element.scrollTo('update');"
222 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
223 }
224 end
225
226 # Bulk edit a set of issues 192 # Bulk edit a set of issues
227 def bulk_edit 193 def bulk_edit
228 @issues.sort! 194 @issues.sort!
229 if request.post?
230 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
231 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
232 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
233
234 unsaved_issue_ids = []
235 @issues.each do |issue|
236 issue.reload
237 journal = issue.init_journal(User.current, params[:notes])
238 issue.safe_attributes = attributes
239 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
240 unless issue.save
241 # Keep unsaved issue ids to display them in flash error
242 unsaved_issue_ids << issue.id
243 end
244 end
245 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
246 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
247 return
248 end
249 @available_statuses = Workflow.available_statuses(@project) 195 @available_statuses = Workflow.available_statuses(@project)
250 @custom_fields = @project.all_issue_custom_fields 196 @custom_fields = @project.all_issue_custom_fields
251 end 197 end
252 198
253 def move 199 def bulk_update
254 @issues.sort! 200 @issues.sort!
255 @copy = params[:copy_options] && params[:copy_options][:copy] 201 attributes = parse_params_for_bulk_issue_attributes(params)
256 @allowed_projects = Issue.allowed_target_projects_on_move 202
257 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] 203 unsaved_issue_ids = []
258 @target_project ||= @project 204 @issues.each do |issue|
259 @trackers = @target_project.trackers 205 issue.reload
260 @available_statuses = Workflow.available_statuses(@project) 206 journal = issue.init_journal(User.current, params[:notes])
261 if request.post? 207 issue.safe_attributes = attributes
262 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) 208 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
263 unsaved_issue_ids = [] 209 unless issue.save
264 moved_issues = [] 210 # Keep unsaved issue ids to display them in flash error
265 @issues.each do |issue| 211 unsaved_issue_ids << issue.id
266 issue.reload 212 end
267 changed_attributes = {} 213 end
268 [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute| 214 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
269 unless params[valid_attribute].blank? 215 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
270 changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
271 end
272 end
273 issue.init_journal(User.current)
274 call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
275 if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
276 moved_issues << r
277 else
278 unsaved_issue_ids << issue.id
279 end
280 end
281 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
282
283 if params[:follow]
284 if @issues.size == 1 && moved_issues.size == 1
285 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
286 else
287 redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
288 end
289 else
290 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
291 end
292 return
293 end
294 render :layout => false if request.xhr?
295 end 216 end
296 217
297 def destroy 218 def destroy
298 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f 219 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
299 if @hours > 0 220 if @hours > 0
322 format.html { redirect_to :action => 'index', :project_id => @project } 243 format.html { redirect_to :action => 'index', :project_id => @project }
323 format.xml { head :ok } 244 format.xml { head :ok }
324 format.json { head :ok } 245 format.json { head :ok }
325 end 246 end
326 end 247 end
327 248
328 def context_menu
329 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
330 if (@issues.size == 1)
331 @issue = @issues.first
332 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
333 end
334 projects = @issues.collect(&:project).compact.uniq
335 @project = projects.first if projects.size == 1
336
337 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
338 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
339 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
340 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
341 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
342 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
343 }
344 if @project
345 @assignables = @project.assignable_users
346 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
347 @trackers = @project.trackers
348 end
349
350 @priorities = IssuePriority.all.reverse
351 @statuses = IssueStatus.find(:all, :order => 'position')
352 @back = params[:back_url] || request.env['HTTP_REFERER']
353
354 render :layout => false
355 end
356
357 def update_form
358 if params[:id].blank?
359 @issue = Issue.new
360 @issue.project = @project
361 else
362 @issue = @project.issues.visible.find(params[:id])
363 end
364 @issue.attributes = params[:issue]
365 @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
366 @priorities = IssuePriority.all
367
368 render :partial => 'attributes'
369 end
370
371 def preview
372 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
373 if @issue
374 @attachements = @issue.attachments
375 @description = params[:issue] && params[:issue][:description]
376 if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
377 @description = nil
378 end
379 @notes = params[:notes]
380 else
381 @description = (params[:issue] ? params[:issue][:description] : nil)
382 end
383 render :layout => false
384 end
385
386 def auto_complete
387 @issues = []
388 q = params[:q].to_s
389 if q.match(/^\d+$/)
390 @issues << @project.issues.visible.find_by_id(q.to_i)
391 end
392 unless q.blank?
393 @issues += @project.issues.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10)
394 end
395 render :layout => false
396 end
397
398 private 249 private
399 def find_issue 250 def find_issue
400 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) 251 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
401 @project = @issue.project 252 @project = @issue.project
402 rescue ActiveRecord::RecordNotFound
403 render_404
404 end
405
406 # Filter for bulk operations
407 def find_issues
408 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
409 raise ActiveRecord::RecordNotFound if @issues.empty?
410 projects = @issues.collect(&:project).compact.uniq
411 if projects.size == 1
412 @project = projects.first
413 else
414 # TODO: let users bulk edit/move/destroy issues from different projects
415 render_error 'Can not bulk edit/move/destroy issues from different projects'
416 return false
417 end
418 rescue ActiveRecord::RecordNotFound 253 rescue ActiveRecord::RecordNotFound
419 render_404 254 render_404
420 end 255 end
421 256
422 def find_project 257 def find_project
447 282
448 end 283 end
449 284
450 # TODO: Refactor, lots of extra code in here 285 # TODO: Refactor, lots of extra code in here
451 def build_new_issue_from_params 286 def build_new_issue_from_params
452 @issue = Issue.new 287 if params[:id].blank?
453 @issue.copy_from(params[:copy_from]) if params[:copy_from] 288 @issue = Issue.new
289 @issue.copy_from(params[:copy_from]) if params[:copy_from]
290 @issue.project = @project
291 else
292 @issue = @project.issues.visible.find(params[:id])
293 end
294
454 @issue.project = @project 295 @issue.project = @project
455 # Tracker must be set before custom field values 296 # Tracker must be set before custom field values
456 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) 297 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
457 if @issue.tracker.nil? 298 if @issue.tracker.nil?
458 render_error l(:error_no_tracker_in_project) 299 render_error l(:error_no_tracker_in_project)
466 @issue.start_date ||= Date.today 307 @issue.start_date ||= Date.today
467 @priorities = IssuePriority.all 308 @priorities = IssuePriority.all
468 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) 309 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
469 end 310 end
470 311
471 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
472 if unsaved_issue_ids.empty?
473 flash[:notice] = l(:notice_successful_update) unless issues.empty?
474 else
475 flash[:error] = l(:notice_failed_to_save_issues,
476 :count => unsaved_issue_ids.size,
477 :total => issues.size,
478 :ids => '#' + unsaved_issue_ids.join(', #'))
479 end
480 end
481
482 def check_for_default_issue_status 312 def check_for_default_issue_status
483 if IssueStatus.default.nil? 313 if IssueStatus.default.nil?
484 render_error l(:error_no_default_issue_status) 314 render_error l(:error_no_default_issue_status)
485 return false 315 return false
486 end 316 end
487 end 317 end
318
319 def parse_params_for_bulk_issue_attributes(params)
320 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
321 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
322 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
323 attributes
324 end
488 end 325 end