##// END OF EJS Templates
Allow bulk edit custom fields of any type (#461)....
Jean-Philippe Lang -
r3164:cab99aa5adf9
parent child
Show More
@@ -1,541 +1,541
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => :new
19 menu_item :new_issue, :only => :new
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :reply]
22 before_filter :find_issue, :only => [:show, :edit, :reply]
23 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
24 before_filter :find_project, :only => [:new, :update_form, :preview]
24 before_filter :find_project, :only => [:new, :update_form, :preview]
25 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :context_menu]
25 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :context_menu]
26 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
27 accept_key_auth :index, :show, :changes
27 accept_key_auth :index, :show, :changes
28
28
29 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
29 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
30
30
31 helper :journals
31 helper :journals
32 helper :projects
32 helper :projects
33 include ProjectsHelper
33 include ProjectsHelper
34 helper :custom_fields
34 helper :custom_fields
35 include CustomFieldsHelper
35 include CustomFieldsHelper
36 helper :issue_relations
36 helper :issue_relations
37 include IssueRelationsHelper
37 include IssueRelationsHelper
38 helper :watchers
38 helper :watchers
39 include WatchersHelper
39 include WatchersHelper
40 helper :attachments
40 helper :attachments
41 include AttachmentsHelper
41 include AttachmentsHelper
42 helper :queries
42 helper :queries
43 helper :sort
43 helper :sort
44 include SortHelper
44 include SortHelper
45 include IssuesHelper
45 include IssuesHelper
46 helper :timelog
46 helper :timelog
47 include Redmine::Export::PDF
47 include Redmine::Export::PDF
48
48
49 verify :method => :post,
49 verify :method => :post,
50 :only => :destroy,
50 :only => :destroy,
51 :render => { :nothing => true, :status => :method_not_allowed }
51 :render => { :nothing => true, :status => :method_not_allowed }
52
52
53 def index
53 def index
54 retrieve_query
54 retrieve_query
55 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
55 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
56 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
56 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
57
57
58 if @query.valid?
58 if @query.valid?
59 limit = per_page_option
59 limit = per_page_option
60 respond_to do |format|
60 respond_to do |format|
61 format.html { }
61 format.html { }
62 format.atom { limit = Setting.feeds_limit.to_i }
62 format.atom { limit = Setting.feeds_limit.to_i }
63 format.csv { limit = Setting.issues_export_limit.to_i }
63 format.csv { limit = Setting.issues_export_limit.to_i }
64 format.pdf { limit = Setting.issues_export_limit.to_i }
64 format.pdf { limit = Setting.issues_export_limit.to_i }
65 end
65 end
66
66
67 @issue_count = @query.issue_count
67 @issue_count = @query.issue_count
68 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
68 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
69 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
69 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
70 :order => sort_clause,
70 :order => sort_clause,
71 :offset => @issue_pages.current.offset,
71 :offset => @issue_pages.current.offset,
72 :limit => limit)
72 :limit => limit)
73 @issue_count_by_group = @query.issue_count_by_group
73 @issue_count_by_group = @query.issue_count_by_group
74
74
75 respond_to do |format|
75 respond_to do |format|
76 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
76 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
77 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
77 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
78 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
78 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
79 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
79 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
80 end
80 end
81 else
81 else
82 # Send html if the query is not valid
82 # Send html if the query is not valid
83 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
83 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
84 end
84 end
85 rescue ActiveRecord::RecordNotFound
85 rescue ActiveRecord::RecordNotFound
86 render_404
86 render_404
87 end
87 end
88
88
89 def changes
89 def changes
90 retrieve_query
90 retrieve_query
91 sort_init 'id', 'desc'
91 sort_init 'id', 'desc'
92 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
92 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
93
93
94 if @query.valid?
94 if @query.valid?
95 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
95 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
96 :limit => 25)
96 :limit => 25)
97 end
97 end
98 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
98 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
99 render :layout => false, :content_type => 'application/atom+xml'
99 render :layout => false, :content_type => 'application/atom+xml'
100 rescue ActiveRecord::RecordNotFound
100 rescue ActiveRecord::RecordNotFound
101 render_404
101 render_404
102 end
102 end
103
103
104 def show
104 def show
105 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
105 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
106 @journals.each_with_index {|j,i| j.indice = i+1}
106 @journals.each_with_index {|j,i| j.indice = i+1}
107 @journals.reverse! if User.current.wants_comments_in_reverse_order?
107 @journals.reverse! if User.current.wants_comments_in_reverse_order?
108 @changesets = @issue.changesets
108 @changesets = @issue.changesets
109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
110 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
110 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
111 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
111 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
112 @priorities = IssuePriority.all
112 @priorities = IssuePriority.all
113 @time_entry = TimeEntry.new
113 @time_entry = TimeEntry.new
114 respond_to do |format|
114 respond_to do |format|
115 format.html { render :template => 'issues/show.rhtml' }
115 format.html { render :template => 'issues/show.rhtml' }
116 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
116 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
117 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
117 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
118 end
118 end
119 end
119 end
120
120
121 # Add a new issue
121 # Add a new issue
122 # The new issue will be created from an existing one if copy_from parameter is given
122 # The new issue will be created from an existing one if copy_from parameter is given
123 def new
123 def new
124 @issue = Issue.new
124 @issue = Issue.new
125 @issue.copy_from(params[:copy_from]) if params[:copy_from]
125 @issue.copy_from(params[:copy_from]) if params[:copy_from]
126 @issue.project = @project
126 @issue.project = @project
127 # Tracker must be set before custom field values
127 # Tracker must be set before custom field values
128 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
128 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
129 if @issue.tracker.nil?
129 if @issue.tracker.nil?
130 render_error l(:error_no_tracker_in_project)
130 render_error l(:error_no_tracker_in_project)
131 return
131 return
132 end
132 end
133 if params[:issue].is_a?(Hash)
133 if params[:issue].is_a?(Hash)
134 @issue.attributes = params[:issue]
134 @issue.attributes = params[:issue]
135 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
135 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
136 end
136 end
137 @issue.author = User.current
137 @issue.author = User.current
138
138
139 default_status = IssueStatus.default
139 default_status = IssueStatus.default
140 unless default_status
140 unless default_status
141 render_error l(:error_no_default_issue_status)
141 render_error l(:error_no_default_issue_status)
142 return
142 return
143 end
143 end
144 @issue.status = default_status
144 @issue.status = default_status
145 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
145 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
146
146
147 if request.get? || request.xhr?
147 if request.get? || request.xhr?
148 @issue.start_date ||= Date.today
148 @issue.start_date ||= Date.today
149 else
149 else
150 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
150 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
151 # Check that the user is allowed to apply the requested status
151 # Check that the user is allowed to apply the requested status
152 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
152 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
153 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
153 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
154 if @issue.save
154 if @issue.save
155 attach_files(@issue, params[:attachments])
155 attach_files(@issue, params[:attachments])
156 flash[:notice] = l(:notice_successful_create)
156 flash[:notice] = l(:notice_successful_create)
157 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
157 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
158 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
158 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
159 { :action => 'show', :id => @issue })
159 { :action => 'show', :id => @issue })
160 return
160 return
161 end
161 end
162 end
162 end
163 @priorities = IssuePriority.all
163 @priorities = IssuePriority.all
164 render :layout => !request.xhr?
164 render :layout => !request.xhr?
165 end
165 end
166
166
167 # Attributes that can be updated on workflow transition (without :edit permission)
167 # Attributes that can be updated on workflow transition (without :edit permission)
168 # TODO: make it configurable (at least per role)
168 # TODO: make it configurable (at least per role)
169 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
169 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
170
170
171 def edit
171 def edit
172 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
172 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
173 @priorities = IssuePriority.all
173 @priorities = IssuePriority.all
174 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
174 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
175 @time_entry = TimeEntry.new
175 @time_entry = TimeEntry.new
176
176
177 @notes = params[:notes]
177 @notes = params[:notes]
178 journal = @issue.init_journal(User.current, @notes)
178 journal = @issue.init_journal(User.current, @notes)
179 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
179 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
180 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
180 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
181 attrs = params[:issue].dup
181 attrs = params[:issue].dup
182 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
182 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
183 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
183 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
184 @issue.attributes = attrs
184 @issue.attributes = attrs
185 end
185 end
186
186
187 if request.post?
187 if request.post?
188 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
188 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
189 @time_entry.attributes = params[:time_entry]
189 @time_entry.attributes = params[:time_entry]
190 attachments = attach_files(@issue, params[:attachments])
190 attachments = attach_files(@issue, params[:attachments])
191 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
191 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
192
192
193 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
193 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
194
194
195 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
195 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
196 # Log spend time
196 # Log spend time
197 if User.current.allowed_to?(:log_time, @project)
197 if User.current.allowed_to?(:log_time, @project)
198 @time_entry.save
198 @time_entry.save
199 end
199 end
200 if !journal.new_record?
200 if !journal.new_record?
201 # Only send notification if something was actually changed
201 # Only send notification if something was actually changed
202 flash[:notice] = l(:notice_successful_update)
202 flash[:notice] = l(:notice_successful_update)
203 end
203 end
204 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
204 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
205 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
205 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
206 end
206 end
207 end
207 end
208 rescue ActiveRecord::StaleObjectError
208 rescue ActiveRecord::StaleObjectError
209 # Optimistic locking exception
209 # Optimistic locking exception
210 flash.now[:error] = l(:notice_locking_conflict)
210 flash.now[:error] = l(:notice_locking_conflict)
211 # Remove the previously added attachments if issue was not updated
211 # Remove the previously added attachments if issue was not updated
212 attachments.each(&:destroy)
212 attachments.each(&:destroy)
213 end
213 end
214
214
215 def reply
215 def reply
216 journal = Journal.find(params[:journal_id]) if params[:journal_id]
216 journal = Journal.find(params[:journal_id]) if params[:journal_id]
217 if journal
217 if journal
218 user = journal.user
218 user = journal.user
219 text = journal.notes
219 text = journal.notes
220 else
220 else
221 user = @issue.author
221 user = @issue.author
222 text = @issue.description
222 text = @issue.description
223 end
223 end
224 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
224 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
225 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
225 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
226 render(:update) { |page|
226 render(:update) { |page|
227 page.<< "$('notes').value = \"#{content}\";"
227 page.<< "$('notes').value = \"#{content}\";"
228 page.show 'update'
228 page.show 'update'
229 page << "Form.Element.focus('notes');"
229 page << "Form.Element.focus('notes');"
230 page << "Element.scrollTo('update');"
230 page << "Element.scrollTo('update');"
231 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
231 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
232 }
232 }
233 end
233 end
234
234
235 # Bulk edit a set of issues
235 # Bulk edit a set of issues
236 def bulk_edit
236 def bulk_edit
237 if request.post?
237 if request.post?
238 tracker = params[:tracker_id].blank? ? nil : @project.trackers.find_by_id(params[:tracker_id])
238 tracker = params[:tracker_id].blank? ? nil : @project.trackers.find_by_id(params[:tracker_id])
239 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
239 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
240 priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
240 priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
241 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
241 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
242 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
242 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
243 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.shared_versions.find_by_id(params[:fixed_version_id])
243 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.shared_versions.find_by_id(params[:fixed_version_id])
244 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
244 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
245
245
246 unsaved_issue_ids = []
246 unsaved_issue_ids = []
247 @issues.each do |issue|
247 @issues.each do |issue|
248 journal = issue.init_journal(User.current, params[:notes])
248 journal = issue.init_journal(User.current, params[:notes])
249 issue.tracker = tracker if tracker
249 issue.tracker = tracker if tracker
250 issue.priority = priority if priority
250 issue.priority = priority if priority
251 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
251 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
252 issue.category = category if category || params[:category_id] == 'none'
252 issue.category = category if category || params[:category_id] == 'none'
253 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
253 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
254 issue.start_date = params[:start_date] unless params[:start_date].blank?
254 issue.start_date = params[:start_date] unless params[:start_date].blank?
255 issue.due_date = params[:due_date] unless params[:due_date].blank?
255 issue.due_date = params[:due_date] unless params[:due_date].blank?
256 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
256 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
257 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
257 issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
258 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
258 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
259 # Don't save any change to the issue if the user is not authorized to apply the requested status
259 # Don't save any change to the issue if the user is not authorized to apply the requested status
260 unless (status.nil? || (issue.new_statuses_allowed_to(User.current).include?(status) && issue.status = status)) && issue.save
260 unless (status.nil? || (issue.new_statuses_allowed_to(User.current).include?(status) && issue.status = status)) && issue.save
261 # Keep unsaved issue ids to display them in flash error
261 # Keep unsaved issue ids to display them in flash error
262 unsaved_issue_ids << issue.id
262 unsaved_issue_ids << issue.id
263 end
263 end
264 end
264 end
265 if unsaved_issue_ids.empty?
265 if unsaved_issue_ids.empty?
266 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
266 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
267 else
267 else
268 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
268 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
269 :total => @issues.size,
269 :total => @issues.size,
270 :ids => '#' + unsaved_issue_ids.join(', #'))
270 :ids => '#' + unsaved_issue_ids.join(', #'))
271 end
271 end
272 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
272 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
273 return
273 return
274 end
274 end
275 @available_statuses = Workflow.available_statuses(@project)
275 @available_statuses = Workflow.available_statuses(@project)
276 @custom_fields = @project.all_issue_custom_fields.select {|f| f.field_format == 'list'}
276 @custom_fields = @project.all_issue_custom_fields
277 end
277 end
278
278
279 def move
279 def move
280 @copy = params[:copy_options] && params[:copy_options][:copy]
280 @copy = params[:copy_options] && params[:copy_options][:copy]
281 @allowed_projects = []
281 @allowed_projects = []
282 # find projects to which the user is allowed to move the issue
282 # find projects to which the user is allowed to move the issue
283 if User.current.admin?
283 if User.current.admin?
284 # admin is allowed to move issues to any active (visible) project
284 # admin is allowed to move issues to any active (visible) project
285 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
285 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
286 else
286 else
287 User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
287 User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
288 end
288 end
289 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
289 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
290 @target_project ||= @project
290 @target_project ||= @project
291 @trackers = @target_project.trackers
291 @trackers = @target_project.trackers
292 @available_statuses = Workflow.available_statuses(@project)
292 @available_statuses = Workflow.available_statuses(@project)
293 if request.post?
293 if request.post?
294 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
294 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
295 unsaved_issue_ids = []
295 unsaved_issue_ids = []
296 moved_issues = []
296 moved_issues = []
297 @issues.each do |issue|
297 @issues.each do |issue|
298 changed_attributes = {}
298 changed_attributes = {}
299 [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
299 [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
300 unless params[valid_attribute].blank?
300 unless params[valid_attribute].blank?
301 changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
301 changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
302 end
302 end
303 end
303 end
304 issue.init_journal(User.current)
304 issue.init_journal(User.current)
305 if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
305 if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
306 moved_issues << r
306 moved_issues << r
307 else
307 else
308 unsaved_issue_ids << issue.id
308 unsaved_issue_ids << issue.id
309 end
309 end
310 end
310 end
311 if unsaved_issue_ids.empty?
311 if unsaved_issue_ids.empty?
312 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
312 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
313 else
313 else
314 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
314 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
315 :total => @issues.size,
315 :total => @issues.size,
316 :ids => '#' + unsaved_issue_ids.join(', #'))
316 :ids => '#' + unsaved_issue_ids.join(', #'))
317 end
317 end
318 if params[:follow]
318 if params[:follow]
319 if @issues.size == 1 && moved_issues.size == 1
319 if @issues.size == 1 && moved_issues.size == 1
320 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
320 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
321 else
321 else
322 redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
322 redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
323 end
323 end
324 else
324 else
325 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
325 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
326 end
326 end
327 return
327 return
328 end
328 end
329 render :layout => false if request.xhr?
329 render :layout => false if request.xhr?
330 end
330 end
331
331
332 def destroy
332 def destroy
333 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
333 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
334 if @hours > 0
334 if @hours > 0
335 case params[:todo]
335 case params[:todo]
336 when 'destroy'
336 when 'destroy'
337 # nothing to do
337 # nothing to do
338 when 'nullify'
338 when 'nullify'
339 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
339 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
340 when 'reassign'
340 when 'reassign'
341 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
341 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
342 if reassign_to.nil?
342 if reassign_to.nil?
343 flash.now[:error] = l(:error_issue_not_found_in_project)
343 flash.now[:error] = l(:error_issue_not_found_in_project)
344 return
344 return
345 else
345 else
346 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
346 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
347 end
347 end
348 else
348 else
349 # display the destroy form
349 # display the destroy form
350 return
350 return
351 end
351 end
352 end
352 end
353 @issues.each(&:destroy)
353 @issues.each(&:destroy)
354 redirect_to :action => 'index', :project_id => @project
354 redirect_to :action => 'index', :project_id => @project
355 end
355 end
356
356
357 def gantt
357 def gantt
358 @gantt = Redmine::Helpers::Gantt.new(params)
358 @gantt = Redmine::Helpers::Gantt.new(params)
359 retrieve_query
359 retrieve_query
360 if @query.valid?
360 if @query.valid?
361 events = []
361 events = []
362 # Issues that have start and due dates
362 # Issues that have start and due dates
363 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
363 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
364 :order => "start_date, due_date",
364 :order => "start_date, due_date",
365 :conditions => ["(((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]
365 :conditions => ["(((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]
366 )
366 )
367 # Issues that don't have a due date but that are assigned to a version with a date
367 # Issues that don't have a due date but that are assigned to a version with a date
368 events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
368 events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
369 :order => "start_date, effective_date",
369 :order => "start_date, effective_date",
370 :conditions => ["(((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]
370 :conditions => ["(((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]
371 )
371 )
372 # Versions
372 # Versions
373 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
373 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
374
374
375 @gantt.events = events
375 @gantt.events = events
376 end
376 end
377
377
378 basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
378 basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
379
379
380 respond_to do |format|
380 respond_to do |format|
381 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
381 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
382 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
382 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
383 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
383 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
384 end
384 end
385 end
385 end
386
386
387 def calendar
387 def calendar
388 if params[:year] and params[:year].to_i > 1900
388 if params[:year] and params[:year].to_i > 1900
389 @year = params[:year].to_i
389 @year = params[:year].to_i
390 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
390 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
391 @month = params[:month].to_i
391 @month = params[:month].to_i
392 end
392 end
393 end
393 end
394 @year ||= Date.today.year
394 @year ||= Date.today.year
395 @month ||= Date.today.month
395 @month ||= Date.today.month
396
396
397 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
397 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
398 retrieve_query
398 retrieve_query
399 if @query.valid?
399 if @query.valid?
400 events = []
400 events = []
401 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
401 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
402 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
402 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
403 )
403 )
404 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
404 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
405
405
406 @calendar.events = events
406 @calendar.events = events
407 end
407 end
408
408
409 render :layout => false if request.xhr?
409 render :layout => false if request.xhr?
410 end
410 end
411
411
412 def context_menu
412 def context_menu
413 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
413 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
414 if (@issues.size == 1)
414 if (@issues.size == 1)
415 @issue = @issues.first
415 @issue = @issues.first
416 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
416 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
417 end
417 end
418 projects = @issues.collect(&:project).compact.uniq
418 projects = @issues.collect(&:project).compact.uniq
419 @project = projects.first if projects.size == 1
419 @project = projects.first if projects.size == 1
420
420
421 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
421 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
422 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
422 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
423 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
423 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
424 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
424 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
425 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
425 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
426 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
426 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
427 }
427 }
428 if @project
428 if @project
429 @assignables = @project.assignable_users
429 @assignables = @project.assignable_users
430 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
430 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
431 @trackers = @project.trackers
431 @trackers = @project.trackers
432 end
432 end
433
433
434 @priorities = IssuePriority.all.reverse
434 @priorities = IssuePriority.all.reverse
435 @statuses = IssueStatus.find(:all, :order => 'position')
435 @statuses = IssueStatus.find(:all, :order => 'position')
436 @back = params[:back_url] || request.env['HTTP_REFERER']
436 @back = params[:back_url] || request.env['HTTP_REFERER']
437
437
438 render :layout => false
438 render :layout => false
439 end
439 end
440
440
441 def update_form
441 def update_form
442 if params[:id].blank?
442 if params[:id].blank?
443 @issue = Issue.new
443 @issue = Issue.new
444 @issue.project = @project
444 @issue.project = @project
445 else
445 else
446 @issue = @project.issues.visible.find(params[:id])
446 @issue = @project.issues.visible.find(params[:id])
447 end
447 end
448 @issue.attributes = params[:issue]
448 @issue.attributes = params[:issue]
449 @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
449 @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
450 @priorities = IssuePriority.all
450 @priorities = IssuePriority.all
451
451
452 render :partial => 'attributes'
452 render :partial => 'attributes'
453 end
453 end
454
454
455 def preview
455 def preview
456 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
456 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
457 @attachements = @issue.attachments if @issue
457 @attachements = @issue.attachments if @issue
458 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
458 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
459 render :partial => 'common/preview'
459 render :partial => 'common/preview'
460 end
460 end
461
461
462 private
462 private
463 def find_issue
463 def find_issue
464 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
464 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
465 @project = @issue.project
465 @project = @issue.project
466 rescue ActiveRecord::RecordNotFound
466 rescue ActiveRecord::RecordNotFound
467 render_404
467 render_404
468 end
468 end
469
469
470 # Filter for bulk operations
470 # Filter for bulk operations
471 def find_issues
471 def find_issues
472 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
472 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
473 raise ActiveRecord::RecordNotFound if @issues.empty?
473 raise ActiveRecord::RecordNotFound if @issues.empty?
474 projects = @issues.collect(&:project).compact.uniq
474 projects = @issues.collect(&:project).compact.uniq
475 if projects.size == 1
475 if projects.size == 1
476 @project = projects.first
476 @project = projects.first
477 else
477 else
478 # TODO: let users bulk edit/move/destroy issues from different projects
478 # TODO: let users bulk edit/move/destroy issues from different projects
479 render_error 'Can not bulk edit/move/destroy issues from different projects'
479 render_error 'Can not bulk edit/move/destroy issues from different projects'
480 return false
480 return false
481 end
481 end
482 rescue ActiveRecord::RecordNotFound
482 rescue ActiveRecord::RecordNotFound
483 render_404
483 render_404
484 end
484 end
485
485
486 def find_project
486 def find_project
487 @project = Project.find(params[:project_id])
487 @project = Project.find(params[:project_id])
488 rescue ActiveRecord::RecordNotFound
488 rescue ActiveRecord::RecordNotFound
489 render_404
489 render_404
490 end
490 end
491
491
492 def find_optional_project
492 def find_optional_project
493 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
493 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
494 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
494 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
495 allowed ? true : deny_access
495 allowed ? true : deny_access
496 rescue ActiveRecord::RecordNotFound
496 rescue ActiveRecord::RecordNotFound
497 render_404
497 render_404
498 end
498 end
499
499
500 # Retrieve query from session or build a new query
500 # Retrieve query from session or build a new query
501 def retrieve_query
501 def retrieve_query
502 if !params[:query_id].blank?
502 if !params[:query_id].blank?
503 cond = "project_id IS NULL"
503 cond = "project_id IS NULL"
504 cond << " OR project_id = #{@project.id}" if @project
504 cond << " OR project_id = #{@project.id}" if @project
505 @query = Query.find(params[:query_id], :conditions => cond)
505 @query = Query.find(params[:query_id], :conditions => cond)
506 @query.project = @project
506 @query.project = @project
507 session[:query] = {:id => @query.id, :project_id => @query.project_id}
507 session[:query] = {:id => @query.id, :project_id => @query.project_id}
508 sort_clear
508 sort_clear
509 else
509 else
510 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
510 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
511 # Give it a name, required to be valid
511 # Give it a name, required to be valid
512 @query = Query.new(:name => "_")
512 @query = Query.new(:name => "_")
513 @query.project = @project
513 @query.project = @project
514 if params[:fields] and params[:fields].is_a? Array
514 if params[:fields] and params[:fields].is_a? Array
515 params[:fields].each do |field|
515 params[:fields].each do |field|
516 @query.add_filter(field, params[:operators][field], params[:values][field])
516 @query.add_filter(field, params[:operators][field], params[:values][field])
517 end
517 end
518 else
518 else
519 @query.available_filters.keys.each do |field|
519 @query.available_filters.keys.each do |field|
520 @query.add_short_filter(field, params[field]) if params[field]
520 @query.add_short_filter(field, params[field]) if params[field]
521 end
521 end
522 end
522 end
523 @query.group_by = params[:group_by]
523 @query.group_by = params[:group_by]
524 @query.column_names = params[:query] && params[:query][:column_names]
524 @query.column_names = params[:query] && params[:query][:column_names]
525 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
525 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
526 else
526 else
527 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
527 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
528 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
528 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
529 @query.project = @project
529 @query.project = @project
530 end
530 end
531 end
531 end
532 end
532 end
533
533
534 # Rescues an invalid query statement. Just in case...
534 # Rescues an invalid query statement. Just in case...
535 def query_statement_invalid(exception)
535 def query_statement_invalid(exception)
536 logger.error "Query::StatementInvalid: #{exception.message}" if logger
536 logger.error "Query::StatementInvalid: #{exception.message}" if logger
537 session.delete(:query)
537 session.delete(:query)
538 sort_clear
538 sort_clear
539 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
539 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
540 end
540 end
541 end
541 end
@@ -1,93 +1,113
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module CustomFieldsHelper
18 module CustomFieldsHelper
19
19
20 def custom_fields_tabs
20 def custom_fields_tabs
21 tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
21 tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
22 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
22 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
23 {:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
23 {:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
24 {:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
24 {:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
25 {:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
25 {:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
26 {:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
26 {:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
27 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
27 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
28 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
28 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
29 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
29 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
30 ]
30 ]
31 end
31 end
32
32
33 # Return custom field html tag corresponding to its format
33 # Return custom field html tag corresponding to its format
34 def custom_field_tag(name, custom_value)
34 def custom_field_tag(name, custom_value)
35 custom_field = custom_value.custom_field
35 custom_field = custom_value.custom_field
36 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
36 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
37 field_id = "#{name}_custom_field_values_#{custom_field.id}"
37 field_id = "#{name}_custom_field_values_#{custom_field.id}"
38
38
39 case custom_field.field_format
39 case custom_field.field_format
40 when "date"
40 when "date"
41 text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
41 text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
42 calendar_for(field_id)
42 calendar_for(field_id)
43 when "text"
43 when "text"
44 text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
44 text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
45 when "bool"
45 when "bool"
46 hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
46 hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
47 when "list"
47 when "list"
48 blank_option = custom_field.is_required? ?
48 blank_option = custom_field.is_required? ?
49 (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
49 (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
50 '<option></option>'
50 '<option></option>'
51 select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
51 select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
52 else
52 else
53 text_field_tag(field_name, custom_value.value, :id => field_id)
53 text_field_tag(field_name, custom_value.value, :id => field_id)
54 end
54 end
55 end
55 end
56
56
57 # Return custom field label tag
57 # Return custom field label tag
58 def custom_field_label_tag(name, custom_value)
58 def custom_field_label_tag(name, custom_value)
59 content_tag "label", custom_value.custom_field.name +
59 content_tag "label", custom_value.custom_field.name +
60 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
60 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
61 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
61 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
62 :class => (custom_value.errors.empty? ? nil : "error" )
62 :class => (custom_value.errors.empty? ? nil : "error" )
63 end
63 end
64
64
65 # Return custom field tag with its label tag
65 # Return custom field tag with its label tag
66 def custom_field_tag_with_label(name, custom_value)
66 def custom_field_tag_with_label(name, custom_value)
67 custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
67 custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
68 end
68 end
69
69
70 def custom_field_tag_for_bulk_edit(custom_field)
71 field_name = "custom_field_values[#{custom_field.id}]"
72 field_id = "custom_field_values_#{custom_field.id}"
73 case custom_field.field_format
74 when "date"
75 text_field_tag(field_name, '', :id => field_id, :size => 10) +
76 calendar_for(field_id)
77 when "text"
78 text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
79 when "bool"
80 select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
81 [l(:general_text_yes), '1'],
82 [l(:general_text_no), '0']]), :id => field_id)
83 when "list"
84 select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values), :id => field_id)
85 else
86 text_field_tag(field_name, '', :id => field_id)
87 end
88 end
89
70 # Return a string used to display a custom value
90 # Return a string used to display a custom value
71 def show_value(custom_value)
91 def show_value(custom_value)
72 return "" unless custom_value
92 return "" unless custom_value
73 format_value(custom_value.value, custom_value.custom_field.field_format)
93 format_value(custom_value.value, custom_value.custom_field.field_format)
74 end
94 end
75
95
76 # Return a string used to display a custom value
96 # Return a string used to display a custom value
77 def format_value(value, field_format)
97 def format_value(value, field_format)
78 return "" unless value && !value.empty?
98 return "" unless value && !value.empty?
79 case field_format
99 case field_format
80 when "date"
100 when "date"
81 begin; format_date(value.to_date); rescue; value end
101 begin; format_date(value.to_date); rescue; value end
82 when "bool"
102 when "bool"
83 l(value == "1" ? :general_text_Yes : :general_text_No)
103 l(value == "1" ? :general_text_Yes : :general_text_No)
84 else
104 else
85 value
105 value
86 end
106 end
87 end
107 end
88
108
89 # Return an array of custom field formats which can be used in select_tag
109 # Return an array of custom field formats which can be used in select_tag
90 def custom_field_formats_for_select
110 def custom_field_formats_for_select
91 CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
111 CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
92 end
112 end
93 end
113 end
@@ -1,64 +1,62
1 <h2><%= l(:label_bulk_edit_selected_issues) %></h2>
1 <h2><%= l(:label_bulk_edit_selected_issues) %></h2>
2
2
3 <ul><%= @issues.collect {|i| content_tag('li', link_to(h("#{i.tracker} ##{i.id}"), { :action => 'show', :id => i }) + h(": #{i.subject}")) }.join("\n") %></ul>
3 <ul><%= @issues.collect {|i| content_tag('li', link_to(h("#{i.tracker} ##{i.id}"), { :action => 'show', :id => i }) + h(": #{i.subject}")) }.join("\n") %></ul>
4
4
5 <% form_tag() do %>
5 <% form_tag() do %>
6 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
6 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
7 <div class="box">
7 <div class="box">
8 <fieldset>
8 <fieldset>
9 <legend><%= l(:label_change_properties) %></legend>
9 <legend><%= l(:label_change_properties) %></legend>
10 <p>
10 <p>
11 <label><%= l(:field_tracker) %>:
11 <label><%= l(:field_tracker) %>:
12 <%= select_tag('tracker_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.trackers, :id, :name)) %></label>
12 <%= select_tag('tracker_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.trackers, :id, :name)) %></label>
13 <% if @available_statuses.any? %>
13 <% if @available_statuses.any? %>
14 <label><%= l(:field_status) %>:
14 <label><%= l(:field_status) %>:
15 <%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %></label>
15 <%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %></label>
16 <% end %>
16 <% end %>
17 </p>
17 </p>
18 <p>
18 <p>
19 <label><%= l(:field_priority) %>:
19 <label><%= l(:field_priority) %>:
20 <%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.all, :id, :name)) %></label>
20 <%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.all, :id, :name)) %></label>
21 <label><%= l(:field_category) %>:
21 <label><%= l(:field_category) %>:
22 <%= select_tag('category_id', content_tag('option', l(:label_no_change_option), :value => '') +
22 <%= select_tag('category_id', content_tag('option', l(:label_no_change_option), :value => '') +
23 content_tag('option', l(:label_none), :value => 'none') +
23 content_tag('option', l(:label_none), :value => 'none') +
24 options_from_collection_for_select(@project.issue_categories, :id, :name)) %></label>
24 options_from_collection_for_select(@project.issue_categories, :id, :name)) %></label>
25 </p>
25 </p>
26 <p>
26 <p>
27 <label><%= l(:field_assigned_to) %>:
27 <label><%= l(:field_assigned_to) %>:
28 <%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') +
28 <%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') +
29 content_tag('option', l(:label_nobody), :value => 'none') +
29 content_tag('option', l(:label_nobody), :value => 'none') +
30 options_from_collection_for_select(@project.assignable_users, :id, :name)) %></label>
30 options_from_collection_for_select(@project.assignable_users, :id, :name)) %></label>
31 <label><%= l(:field_fixed_version) %>:
31 <label><%= l(:field_fixed_version) %>:
32 <%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
32 <%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
33 content_tag('option', l(:label_none), :value => 'none') +
33 content_tag('option', l(:label_none), :value => 'none') +
34 version_options_for_select(@project.shared_versions.open)) %></label>
34 version_options_for_select(@project.shared_versions.open)) %></label>
35 </p>
35 </p>
36
36
37 <p>
37 <p>
38 <label><%= l(:field_start_date) %>:
38 <label><%= l(:field_start_date) %>:
39 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label>
39 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label>
40 <label><%= l(:field_due_date) %>:
40 <label><%= l(:field_due_date) %>:
41 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label>
41 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label>
42 <% if Issue.use_field_for_done_ratio? %>
42 <% if Issue.use_field_for_done_ratio? %>
43 <label><%= l(:field_done_ratio) %>:
43 <label><%= l(:field_done_ratio) %>:
44 <%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
44 <%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
45 <% end %>
45 <% end %>
46 </p>
46 </p>
47
47
48 <% @custom_fields.each do |custom_field| %>
48 <% @custom_fields.each do |custom_field| %>
49 <p><label><%= h(custom_field.name) %>
49 <p><label><%= h(custom_field.name) %> <%= custom_field_tag_for_bulk_edit(custom_field) %></label></p>
50 <%= select_tag "custom_field_values[#{custom_field.id}]", options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values) %></label>
51 </p>
52 <% end %>
50 <% end %>
53
51
54 <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
52 <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
55 </fieldset>
53 </fieldset>
56
54
57 <fieldset><legend><%= l(:field_notes) %></legend>
55 <fieldset><legend><%= l(:field_notes) %></legend>
58 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
56 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
59 <%= wikitoolbar_for 'notes' %>
57 <%= wikitoolbar_for 'notes' %>
60 </fieldset>
58 </fieldset>
61 </div>
59 </div>
62
60
63 <p><%= submit_tag l(:button_submit) %></p>
61 <p><%= submit_tag l(:button_submit) %></p>
64 <% end %>
62 <% end %>
@@ -1,117 +1,131
1 ---
1 ---
2 custom_fields_001:
2 custom_fields_001:
3 name: Database
3 name: Database
4 min_length: 0
4 min_length: 0
5 regexp: ""
5 regexp: ""
6 is_for_all: true
6 is_for_all: true
7 is_filter: true
7 is_filter: true
8 type: IssueCustomField
8 type: IssueCustomField
9 max_length: 0
9 max_length: 0
10 possible_values:
10 possible_values:
11 - MySQL
11 - MySQL
12 - PostgreSQL
12 - PostgreSQL
13 - Oracle
13 - Oracle
14 id: 1
14 id: 1
15 is_required: false
15 is_required: false
16 field_format: list
16 field_format: list
17 default_value: ""
17 default_value: ""
18 editable: true
18 editable: true
19 custom_fields_002:
19 custom_fields_002:
20 name: Searchable field
20 name: Searchable field
21 min_length: 1
21 min_length: 1
22 regexp: ""
22 regexp: ""
23 is_for_all: true
23 is_for_all: true
24 type: IssueCustomField
24 type: IssueCustomField
25 max_length: 100
25 max_length: 100
26 possible_values: ""
26 possible_values: ""
27 id: 2
27 id: 2
28 is_required: false
28 is_required: false
29 field_format: string
29 field_format: string
30 searchable: true
30 searchable: true
31 default_value: "Default string"
31 default_value: "Default string"
32 editable: true
32 editable: true
33 custom_fields_003:
33 custom_fields_003:
34 name: Development status
34 name: Development status
35 min_length: 0
35 min_length: 0
36 regexp: ""
36 regexp: ""
37 is_for_all: false
37 is_for_all: false
38 is_filter: true
38 is_filter: true
39 type: ProjectCustomField
39 type: ProjectCustomField
40 max_length: 0
40 max_length: 0
41 possible_values:
41 possible_values:
42 - Stable
42 - Stable
43 - Beta
43 - Beta
44 - Alpha
44 - Alpha
45 - Planning
45 - Planning
46 id: 3
46 id: 3
47 is_required: true
47 is_required: true
48 field_format: list
48 field_format: list
49 default_value: ""
49 default_value: ""
50 editable: true
50 editable: true
51 custom_fields_004:
51 custom_fields_004:
52 name: Phone number
52 name: Phone number
53 min_length: 0
53 min_length: 0
54 regexp: ""
54 regexp: ""
55 is_for_all: false
55 is_for_all: false
56 type: UserCustomField
56 type: UserCustomField
57 max_length: 0
57 max_length: 0
58 possible_values: ""
58 possible_values: ""
59 id: 4
59 id: 4
60 is_required: false
60 is_required: false
61 field_format: string
61 field_format: string
62 default_value: ""
62 default_value: ""
63 editable: true
63 editable: true
64 custom_fields_005:
64 custom_fields_005:
65 name: Money
65 name: Money
66 min_length: 0
66 min_length: 0
67 regexp: ""
67 regexp: ""
68 is_for_all: false
68 is_for_all: false
69 type: UserCustomField
69 type: UserCustomField
70 max_length: 0
70 max_length: 0
71 possible_values: ""
71 possible_values: ""
72 id: 5
72 id: 5
73 is_required: false
73 is_required: false
74 field_format: float
74 field_format: float
75 default_value: ""
75 default_value: ""
76 editable: true
76 editable: true
77 custom_fields_006:
77 custom_fields_006:
78 name: Float field
78 name: Float field
79 min_length: 0
79 min_length: 0
80 regexp: ""
80 regexp: ""
81 is_for_all: true
81 is_for_all: true
82 type: IssueCustomField
82 type: IssueCustomField
83 max_length: 0
83 max_length: 0
84 possible_values: ""
84 possible_values: ""
85 id: 6
85 id: 6
86 is_required: false
86 is_required: false
87 field_format: float
87 field_format: float
88 default_value: ""
88 default_value: ""
89 editable: true
89 editable: true
90 custom_fields_007:
90 custom_fields_007:
91 name: Billable
91 name: Billable
92 min_length: 0
92 min_length: 0
93 regexp: ""
93 regexp: ""
94 is_for_all: false
94 is_for_all: false
95 is_filter: true
95 is_filter: true
96 type: TimeEntryActivityCustomField
96 type: TimeEntryActivityCustomField
97 max_length: 0
97 max_length: 0
98 possible_values: ""
98 possible_values: ""
99 id: 7
99 id: 7
100 is_required: false
100 is_required: false
101 field_format: bool
101 field_format: bool
102 default_value: ""
102 default_value: ""
103 editable: true
103 editable: true
104 custom_fields_008:
104 custom_fields_008:
105 name: Custom date
105 name: Custom date
106 min_length: 0
106 min_length: 0
107 regexp: ""
107 regexp: ""
108 is_for_all: true
108 is_for_all: true
109 is_filter: false
109 is_filter: false
110 type: IssueCustomField
110 type: IssueCustomField
111 max_length: 0
111 max_length: 0
112 possible_values: ""
112 possible_values: ""
113 id: 8
113 id: 8
114 is_required: false
114 is_required: false
115 field_format: date
115 field_format: date
116 default_value: ""
116 default_value: ""
117 editable: true
117 editable: true
118 custom_fields_009:
119 name: Project 1 cf
120 min_length: 0
121 regexp: ""
122 is_for_all: false
123 is_filter: true
124 type: IssueCustomField
125 max_length: 0
126 possible_values: ""
127 id: 9
128 is_required: false
129 field_format: date
130 default_value: ""
131 editable: true
@@ -1,2 +1,4
1 --- {}
1 ---
2
2 custom_fields_projects_001:
3 custom_field_id: 9
4 project_id: 1
@@ -1,1276 +1,1284
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
22 class IssuesController; def rescue_action(e) raise e end; end
23
23
24 class IssuesControllerTest < ActionController::TestCase
24 class IssuesControllerTest < ActionController::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :member_roles,
29 :member_roles,
30 :issues,
30 :issues,
31 :issue_statuses,
31 :issue_statuses,
32 :versions,
32 :versions,
33 :trackers,
33 :trackers,
34 :projects_trackers,
34 :projects_trackers,
35 :issue_categories,
35 :issue_categories,
36 :enabled_modules,
36 :enabled_modules,
37 :enumerations,
37 :enumerations,
38 :attachments,
38 :attachments,
39 :workflows,
39 :workflows,
40 :custom_fields,
40 :custom_fields,
41 :custom_values,
41 :custom_values,
42 :custom_fields_projects,
42 :custom_fields_projects,
43 :custom_fields_trackers,
43 :custom_fields_trackers,
44 :time_entries,
44 :time_entries,
45 :journals,
45 :journals,
46 :journal_details,
46 :journal_details,
47 :queries
47 :queries
48
48
49 def setup
49 def setup
50 @controller = IssuesController.new
50 @controller = IssuesController.new
51 @request = ActionController::TestRequest.new
51 @request = ActionController::TestRequest.new
52 @response = ActionController::TestResponse.new
52 @response = ActionController::TestResponse.new
53 User.current = nil
53 User.current = nil
54 end
54 end
55
55
56 def test_index_routing
56 def test_index_routing
57 assert_routing(
57 assert_routing(
58 {:method => :get, :path => '/issues'},
58 {:method => :get, :path => '/issues'},
59 :controller => 'issues', :action => 'index'
59 :controller => 'issues', :action => 'index'
60 )
60 )
61 end
61 end
62
62
63 def test_index
63 def test_index
64 Setting.default_language = 'en'
64 Setting.default_language = 'en'
65
65
66 get :index
66 get :index
67 assert_response :success
67 assert_response :success
68 assert_template 'index.rhtml'
68 assert_template 'index.rhtml'
69 assert_not_nil assigns(:issues)
69 assert_not_nil assigns(:issues)
70 assert_nil assigns(:project)
70 assert_nil assigns(:project)
71 assert_tag :tag => 'a', :content => /Can't print recipes/
71 assert_tag :tag => 'a', :content => /Can't print recipes/
72 assert_tag :tag => 'a', :content => /Subproject issue/
72 assert_tag :tag => 'a', :content => /Subproject issue/
73 # private projects hidden
73 # private projects hidden
74 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
74 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
75 assert_no_tag :tag => 'a', :content => /Issue on project 2/
75 assert_no_tag :tag => 'a', :content => /Issue on project 2/
76 # project column
76 # project column
77 assert_tag :tag => 'th', :content => /Project/
77 assert_tag :tag => 'th', :content => /Project/
78 end
78 end
79
79
80 def test_index_should_not_list_issues_when_module_disabled
80 def test_index_should_not_list_issues_when_module_disabled
81 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
81 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
82 get :index
82 get :index
83 assert_response :success
83 assert_response :success
84 assert_template 'index.rhtml'
84 assert_template 'index.rhtml'
85 assert_not_nil assigns(:issues)
85 assert_not_nil assigns(:issues)
86 assert_nil assigns(:project)
86 assert_nil assigns(:project)
87 assert_no_tag :tag => 'a', :content => /Can't print recipes/
87 assert_no_tag :tag => 'a', :content => /Can't print recipes/
88 assert_tag :tag => 'a', :content => /Subproject issue/
88 assert_tag :tag => 'a', :content => /Subproject issue/
89 end
89 end
90
90
91 def test_index_with_project_routing
91 def test_index_with_project_routing
92 assert_routing(
92 assert_routing(
93 {:method => :get, :path => '/projects/23/issues'},
93 {:method => :get, :path => '/projects/23/issues'},
94 :controller => 'issues', :action => 'index', :project_id => '23'
94 :controller => 'issues', :action => 'index', :project_id => '23'
95 )
95 )
96 end
96 end
97
97
98 def test_index_should_not_list_issues_when_module_disabled
98 def test_index_should_not_list_issues_when_module_disabled
99 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
99 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
100 get :index
100 get :index
101 assert_response :success
101 assert_response :success
102 assert_template 'index.rhtml'
102 assert_template 'index.rhtml'
103 assert_not_nil assigns(:issues)
103 assert_not_nil assigns(:issues)
104 assert_nil assigns(:project)
104 assert_nil assigns(:project)
105 assert_no_tag :tag => 'a', :content => /Can't print recipes/
105 assert_no_tag :tag => 'a', :content => /Can't print recipes/
106 assert_tag :tag => 'a', :content => /Subproject issue/
106 assert_tag :tag => 'a', :content => /Subproject issue/
107 end
107 end
108
108
109 def test_index_with_project_routing
109 def test_index_with_project_routing
110 assert_routing(
110 assert_routing(
111 {:method => :get, :path => 'projects/23/issues'},
111 {:method => :get, :path => 'projects/23/issues'},
112 :controller => 'issues', :action => 'index', :project_id => '23'
112 :controller => 'issues', :action => 'index', :project_id => '23'
113 )
113 )
114 end
114 end
115
115
116 def test_index_with_project
116 def test_index_with_project
117 Setting.display_subprojects_issues = 0
117 Setting.display_subprojects_issues = 0
118 get :index, :project_id => 1
118 get :index, :project_id => 1
119 assert_response :success
119 assert_response :success
120 assert_template 'index.rhtml'
120 assert_template 'index.rhtml'
121 assert_not_nil assigns(:issues)
121 assert_not_nil assigns(:issues)
122 assert_tag :tag => 'a', :content => /Can't print recipes/
122 assert_tag :tag => 'a', :content => /Can't print recipes/
123 assert_no_tag :tag => 'a', :content => /Subproject issue/
123 assert_no_tag :tag => 'a', :content => /Subproject issue/
124 end
124 end
125
125
126 def test_index_with_project_and_subprojects
126 def test_index_with_project_and_subprojects
127 Setting.display_subprojects_issues = 1
127 Setting.display_subprojects_issues = 1
128 get :index, :project_id => 1
128 get :index, :project_id => 1
129 assert_response :success
129 assert_response :success
130 assert_template 'index.rhtml'
130 assert_template 'index.rhtml'
131 assert_not_nil assigns(:issues)
131 assert_not_nil assigns(:issues)
132 assert_tag :tag => 'a', :content => /Can't print recipes/
132 assert_tag :tag => 'a', :content => /Can't print recipes/
133 assert_tag :tag => 'a', :content => /Subproject issue/
133 assert_tag :tag => 'a', :content => /Subproject issue/
134 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
134 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
135 end
135 end
136
136
137 def test_index_with_project_and_subprojects_should_show_private_subprojects
137 def test_index_with_project_and_subprojects_should_show_private_subprojects
138 @request.session[:user_id] = 2
138 @request.session[:user_id] = 2
139 Setting.display_subprojects_issues = 1
139 Setting.display_subprojects_issues = 1
140 get :index, :project_id => 1
140 get :index, :project_id => 1
141 assert_response :success
141 assert_response :success
142 assert_template 'index.rhtml'
142 assert_template 'index.rhtml'
143 assert_not_nil assigns(:issues)
143 assert_not_nil assigns(:issues)
144 assert_tag :tag => 'a', :content => /Can't print recipes/
144 assert_tag :tag => 'a', :content => /Can't print recipes/
145 assert_tag :tag => 'a', :content => /Subproject issue/
145 assert_tag :tag => 'a', :content => /Subproject issue/
146 assert_tag :tag => 'a', :content => /Issue of a private subproject/
146 assert_tag :tag => 'a', :content => /Issue of a private subproject/
147 end
147 end
148
148
149 def test_index_with_project_routing_formatted
149 def test_index_with_project_routing_formatted
150 assert_routing(
150 assert_routing(
151 {:method => :get, :path => 'projects/23/issues.pdf'},
151 {:method => :get, :path => 'projects/23/issues.pdf'},
152 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
152 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
153 )
153 )
154 assert_routing(
154 assert_routing(
155 {:method => :get, :path => 'projects/23/issues.atom'},
155 {:method => :get, :path => 'projects/23/issues.atom'},
156 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
156 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
157 )
157 )
158 end
158 end
159
159
160 def test_index_with_project_and_filter
160 def test_index_with_project_and_filter
161 get :index, :project_id => 1, :set_filter => 1
161 get :index, :project_id => 1, :set_filter => 1
162 assert_response :success
162 assert_response :success
163 assert_template 'index.rhtml'
163 assert_template 'index.rhtml'
164 assert_not_nil assigns(:issues)
164 assert_not_nil assigns(:issues)
165 end
165 end
166
166
167 def test_index_with_query
167 def test_index_with_query
168 get :index, :project_id => 1, :query_id => 5
168 get :index, :project_id => 1, :query_id => 5
169 assert_response :success
169 assert_response :success
170 assert_template 'index.rhtml'
170 assert_template 'index.rhtml'
171 assert_not_nil assigns(:issues)
171 assert_not_nil assigns(:issues)
172 assert_nil assigns(:issue_count_by_group)
172 assert_nil assigns(:issue_count_by_group)
173 end
173 end
174
174
175 def test_index_with_query_grouped_by_tracker
175 def test_index_with_query_grouped_by_tracker
176 get :index, :project_id => 1, :query_id => 6
176 get :index, :project_id => 1, :query_id => 6
177 assert_response :success
177 assert_response :success
178 assert_template 'index.rhtml'
178 assert_template 'index.rhtml'
179 assert_not_nil assigns(:issues)
179 assert_not_nil assigns(:issues)
180 assert_not_nil assigns(:issue_count_by_group)
180 assert_not_nil assigns(:issue_count_by_group)
181 end
181 end
182
182
183 def test_index_with_query_grouped_by_list_custom_field
183 def test_index_with_query_grouped_by_list_custom_field
184 get :index, :project_id => 1, :query_id => 9
184 get :index, :project_id => 1, :query_id => 9
185 assert_response :success
185 assert_response :success
186 assert_template 'index.rhtml'
186 assert_template 'index.rhtml'
187 assert_not_nil assigns(:issues)
187 assert_not_nil assigns(:issues)
188 assert_not_nil assigns(:issue_count_by_group)
188 assert_not_nil assigns(:issue_count_by_group)
189 end
189 end
190
190
191 def test_index_sort_by_field_not_included_in_columns
191 def test_index_sort_by_field_not_included_in_columns
192 Setting.issue_list_default_columns = %w(subject author)
192 Setting.issue_list_default_columns = %w(subject author)
193 get :index, :sort => 'tracker'
193 get :index, :sort => 'tracker'
194 end
194 end
195
195
196 def test_index_csv_with_project
196 def test_index_csv_with_project
197 Setting.default_language = 'en'
197 Setting.default_language = 'en'
198
198
199 get :index, :format => 'csv'
199 get :index, :format => 'csv'
200 assert_response :success
200 assert_response :success
201 assert_not_nil assigns(:issues)
201 assert_not_nil assigns(:issues)
202 assert_equal 'text/csv', @response.content_type
202 assert_equal 'text/csv', @response.content_type
203 assert @response.body.starts_with?("#,")
203 assert @response.body.starts_with?("#,")
204
204
205 get :index, :project_id => 1, :format => 'csv'
205 get :index, :project_id => 1, :format => 'csv'
206 assert_response :success
206 assert_response :success
207 assert_not_nil assigns(:issues)
207 assert_not_nil assigns(:issues)
208 assert_equal 'text/csv', @response.content_type
208 assert_equal 'text/csv', @response.content_type
209 end
209 end
210
210
211 def test_index_formatted
211 def test_index_formatted
212 assert_routing(
212 assert_routing(
213 {:method => :get, :path => 'issues.pdf'},
213 {:method => :get, :path => 'issues.pdf'},
214 :controller => 'issues', :action => 'index', :format => 'pdf'
214 :controller => 'issues', :action => 'index', :format => 'pdf'
215 )
215 )
216 assert_routing(
216 assert_routing(
217 {:method => :get, :path => 'issues.atom'},
217 {:method => :get, :path => 'issues.atom'},
218 :controller => 'issues', :action => 'index', :format => 'atom'
218 :controller => 'issues', :action => 'index', :format => 'atom'
219 )
219 )
220 end
220 end
221
221
222 def test_index_pdf
222 def test_index_pdf
223 get :index, :format => 'pdf'
223 get :index, :format => 'pdf'
224 assert_response :success
224 assert_response :success
225 assert_not_nil assigns(:issues)
225 assert_not_nil assigns(:issues)
226 assert_equal 'application/pdf', @response.content_type
226 assert_equal 'application/pdf', @response.content_type
227
227
228 get :index, :project_id => 1, :format => 'pdf'
228 get :index, :project_id => 1, :format => 'pdf'
229 assert_response :success
229 assert_response :success
230 assert_not_nil assigns(:issues)
230 assert_not_nil assigns(:issues)
231 assert_equal 'application/pdf', @response.content_type
231 assert_equal 'application/pdf', @response.content_type
232
232
233 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
233 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
234 assert_response :success
234 assert_response :success
235 assert_not_nil assigns(:issues)
235 assert_not_nil assigns(:issues)
236 assert_equal 'application/pdf', @response.content_type
236 assert_equal 'application/pdf', @response.content_type
237 end
237 end
238
238
239 def test_index_sort
239 def test_index_sort
240 get :index, :sort => 'tracker,id:desc'
240 get :index, :sort => 'tracker,id:desc'
241 assert_response :success
241 assert_response :success
242
242
243 sort_params = @request.session['issues_index_sort']
243 sort_params = @request.session['issues_index_sort']
244 assert sort_params.is_a?(String)
244 assert sort_params.is_a?(String)
245 assert_equal 'tracker,id:desc', sort_params
245 assert_equal 'tracker,id:desc', sort_params
246
246
247 issues = assigns(:issues)
247 issues = assigns(:issues)
248 assert_not_nil issues
248 assert_not_nil issues
249 assert !issues.empty?
249 assert !issues.empty?
250 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
250 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
251 end
251 end
252
252
253 def test_index_with_columns
253 def test_index_with_columns
254 columns = ['tracker', 'subject', 'assigned_to']
254 columns = ['tracker', 'subject', 'assigned_to']
255 get :index, :set_filter => 1, :query => { 'column_names' => columns}
255 get :index, :set_filter => 1, :query => { 'column_names' => columns}
256 assert_response :success
256 assert_response :success
257
257
258 # query should use specified columns
258 # query should use specified columns
259 query = assigns(:query)
259 query = assigns(:query)
260 assert_kind_of Query, query
260 assert_kind_of Query, query
261 assert_equal columns, query.column_names.map(&:to_s)
261 assert_equal columns, query.column_names.map(&:to_s)
262
262
263 # columns should be stored in session
263 # columns should be stored in session
264 assert_kind_of Hash, session[:query]
264 assert_kind_of Hash, session[:query]
265 assert_kind_of Array, session[:query][:column_names]
265 assert_kind_of Array, session[:query][:column_names]
266 assert_equal columns, session[:query][:column_names].map(&:to_s)
266 assert_equal columns, session[:query][:column_names].map(&:to_s)
267 end
267 end
268
268
269 def test_gantt
269 def test_gantt
270 get :gantt, :project_id => 1
270 get :gantt, :project_id => 1
271 assert_response :success
271 assert_response :success
272 assert_template 'gantt.rhtml'
272 assert_template 'gantt.rhtml'
273 assert_not_nil assigns(:gantt)
273 assert_not_nil assigns(:gantt)
274 events = assigns(:gantt).events
274 events = assigns(:gantt).events
275 assert_not_nil events
275 assert_not_nil events
276 # Issue with start and due dates
276 # Issue with start and due dates
277 i = Issue.find(1)
277 i = Issue.find(1)
278 assert_not_nil i.due_date
278 assert_not_nil i.due_date
279 assert events.include?(Issue.find(1))
279 assert events.include?(Issue.find(1))
280 # Issue with without due date but targeted to a version with date
280 # Issue with without due date but targeted to a version with date
281 i = Issue.find(2)
281 i = Issue.find(2)
282 assert_nil i.due_date
282 assert_nil i.due_date
283 assert events.include?(i)
283 assert events.include?(i)
284 end
284 end
285
285
286 def test_cross_project_gantt
286 def test_cross_project_gantt
287 get :gantt
287 get :gantt
288 assert_response :success
288 assert_response :success
289 assert_template 'gantt.rhtml'
289 assert_template 'gantt.rhtml'
290 assert_not_nil assigns(:gantt)
290 assert_not_nil assigns(:gantt)
291 events = assigns(:gantt).events
291 events = assigns(:gantt).events
292 assert_not_nil events
292 assert_not_nil events
293 end
293 end
294
294
295 def test_gantt_export_to_pdf
295 def test_gantt_export_to_pdf
296 get :gantt, :project_id => 1, :format => 'pdf'
296 get :gantt, :project_id => 1, :format => 'pdf'
297 assert_response :success
297 assert_response :success
298 assert_equal 'application/pdf', @response.content_type
298 assert_equal 'application/pdf', @response.content_type
299 assert @response.body.starts_with?('%PDF')
299 assert @response.body.starts_with?('%PDF')
300 assert_not_nil assigns(:gantt)
300 assert_not_nil assigns(:gantt)
301 end
301 end
302
302
303 def test_cross_project_gantt_export_to_pdf
303 def test_cross_project_gantt_export_to_pdf
304 get :gantt, :format => 'pdf'
304 get :gantt, :format => 'pdf'
305 assert_response :success
305 assert_response :success
306 assert_equal 'application/pdf', @response.content_type
306 assert_equal 'application/pdf', @response.content_type
307 assert @response.body.starts_with?('%PDF')
307 assert @response.body.starts_with?('%PDF')
308 assert_not_nil assigns(:gantt)
308 assert_not_nil assigns(:gantt)
309 end
309 end
310
310
311 if Object.const_defined?(:Magick)
311 if Object.const_defined?(:Magick)
312 def test_gantt_image
312 def test_gantt_image
313 get :gantt, :project_id => 1, :format => 'png'
313 get :gantt, :project_id => 1, :format => 'png'
314 assert_response :success
314 assert_response :success
315 assert_equal 'image/png', @response.content_type
315 assert_equal 'image/png', @response.content_type
316 end
316 end
317 else
317 else
318 puts "RMagick not installed. Skipping tests !!!"
318 puts "RMagick not installed. Skipping tests !!!"
319 end
319 end
320
320
321 def test_calendar
321 def test_calendar
322 get :calendar, :project_id => 1
322 get :calendar, :project_id => 1
323 assert_response :success
323 assert_response :success
324 assert_template 'calendar'
324 assert_template 'calendar'
325 assert_not_nil assigns(:calendar)
325 assert_not_nil assigns(:calendar)
326 end
326 end
327
327
328 def test_cross_project_calendar
328 def test_cross_project_calendar
329 get :calendar
329 get :calendar
330 assert_response :success
330 assert_response :success
331 assert_template 'calendar'
331 assert_template 'calendar'
332 assert_not_nil assigns(:calendar)
332 assert_not_nil assigns(:calendar)
333 end
333 end
334
334
335 def test_changes
335 def test_changes
336 get :changes, :project_id => 1
336 get :changes, :project_id => 1
337 assert_response :success
337 assert_response :success
338 assert_not_nil assigns(:journals)
338 assert_not_nil assigns(:journals)
339 assert_equal 'application/atom+xml', @response.content_type
339 assert_equal 'application/atom+xml', @response.content_type
340 end
340 end
341
341
342 def test_show_routing
342 def test_show_routing
343 assert_routing(
343 assert_routing(
344 {:method => :get, :path => '/issues/64'},
344 {:method => :get, :path => '/issues/64'},
345 :controller => 'issues', :action => 'show', :id => '64'
345 :controller => 'issues', :action => 'show', :id => '64'
346 )
346 )
347 end
347 end
348
348
349 def test_show_routing_formatted
349 def test_show_routing_formatted
350 assert_routing(
350 assert_routing(
351 {:method => :get, :path => '/issues/2332.pdf'},
351 {:method => :get, :path => '/issues/2332.pdf'},
352 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
352 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
353 )
353 )
354 assert_routing(
354 assert_routing(
355 {:method => :get, :path => '/issues/23123.atom'},
355 {:method => :get, :path => '/issues/23123.atom'},
356 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
356 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
357 )
357 )
358 end
358 end
359
359
360 def test_show_by_anonymous
360 def test_show_by_anonymous
361 get :show, :id => 1
361 get :show, :id => 1
362 assert_response :success
362 assert_response :success
363 assert_template 'show.rhtml'
363 assert_template 'show.rhtml'
364 assert_not_nil assigns(:issue)
364 assert_not_nil assigns(:issue)
365 assert_equal Issue.find(1), assigns(:issue)
365 assert_equal Issue.find(1), assigns(:issue)
366
366
367 # anonymous role is allowed to add a note
367 # anonymous role is allowed to add a note
368 assert_tag :tag => 'form',
368 assert_tag :tag => 'form',
369 :descendant => { :tag => 'fieldset',
369 :descendant => { :tag => 'fieldset',
370 :child => { :tag => 'legend',
370 :child => { :tag => 'legend',
371 :content => /Notes/ } }
371 :content => /Notes/ } }
372 end
372 end
373
373
374 def test_show_by_manager
374 def test_show_by_manager
375 @request.session[:user_id] = 2
375 @request.session[:user_id] = 2
376 get :show, :id => 1
376 get :show, :id => 1
377 assert_response :success
377 assert_response :success
378
378
379 assert_tag :tag => 'form',
379 assert_tag :tag => 'form',
380 :descendant => { :tag => 'fieldset',
380 :descendant => { :tag => 'fieldset',
381 :child => { :tag => 'legend',
381 :child => { :tag => 'legend',
382 :content => /Change properties/ } },
382 :content => /Change properties/ } },
383 :descendant => { :tag => 'fieldset',
383 :descendant => { :tag => 'fieldset',
384 :child => { :tag => 'legend',
384 :child => { :tag => 'legend',
385 :content => /Log time/ } },
385 :content => /Log time/ } },
386 :descendant => { :tag => 'fieldset',
386 :descendant => { :tag => 'fieldset',
387 :child => { :tag => 'legend',
387 :child => { :tag => 'legend',
388 :content => /Notes/ } }
388 :content => /Notes/ } }
389 end
389 end
390
390
391 def test_show_should_deny_anonymous_access_without_permission
391 def test_show_should_deny_anonymous_access_without_permission
392 Role.anonymous.remove_permission!(:view_issues)
392 Role.anonymous.remove_permission!(:view_issues)
393 get :show, :id => 1
393 get :show, :id => 1
394 assert_response :redirect
394 assert_response :redirect
395 end
395 end
396
396
397 def test_show_should_deny_non_member_access_without_permission
397 def test_show_should_deny_non_member_access_without_permission
398 Role.non_member.remove_permission!(:view_issues)
398 Role.non_member.remove_permission!(:view_issues)
399 @request.session[:user_id] = 9
399 @request.session[:user_id] = 9
400 get :show, :id => 1
400 get :show, :id => 1
401 assert_response 403
401 assert_response 403
402 end
402 end
403
403
404 def test_show_should_deny_member_access_without_permission
404 def test_show_should_deny_member_access_without_permission
405 Role.find(1).remove_permission!(:view_issues)
405 Role.find(1).remove_permission!(:view_issues)
406 @request.session[:user_id] = 2
406 @request.session[:user_id] = 2
407 get :show, :id => 1
407 get :show, :id => 1
408 assert_response 403
408 assert_response 403
409 end
409 end
410
410
411 def test_show_should_not_disclose_relations_to_invisible_issues
411 def test_show_should_not_disclose_relations_to_invisible_issues
412 Setting.cross_project_issue_relations = '1'
412 Setting.cross_project_issue_relations = '1'
413 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
413 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
414 # Relation to a private project issue
414 # Relation to a private project issue
415 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
415 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
416
416
417 get :show, :id => 1
417 get :show, :id => 1
418 assert_response :success
418 assert_response :success
419
419
420 assert_tag :div, :attributes => { :id => 'relations' },
420 assert_tag :div, :attributes => { :id => 'relations' },
421 :descendant => { :tag => 'a', :content => /#2$/ }
421 :descendant => { :tag => 'a', :content => /#2$/ }
422 assert_no_tag :div, :attributes => { :id => 'relations' },
422 assert_no_tag :div, :attributes => { :id => 'relations' },
423 :descendant => { :tag => 'a', :content => /#4$/ }
423 :descendant => { :tag => 'a', :content => /#4$/ }
424 end
424 end
425
425
426 def test_show_atom
426 def test_show_atom
427 get :show, :id => 2, :format => 'atom'
427 get :show, :id => 2, :format => 'atom'
428 assert_response :success
428 assert_response :success
429 assert_template 'changes.rxml'
429 assert_template 'changes.rxml'
430 # Inline image
430 # Inline image
431 assert @response.body.include?("&lt;img src=\"http://test.host/attachments/download/10\" alt=\"\" /&gt;"), "Body did not match. Body: #{@response.body}"
431 assert @response.body.include?("&lt;img src=\"http://test.host/attachments/download/10\" alt=\"\" /&gt;"), "Body did not match. Body: #{@response.body}"
432 end
432 end
433
433
434 def test_new_routing
434 def test_new_routing
435 assert_routing(
435 assert_routing(
436 {:method => :get, :path => '/projects/1/issues/new'},
436 {:method => :get, :path => '/projects/1/issues/new'},
437 :controller => 'issues', :action => 'new', :project_id => '1'
437 :controller => 'issues', :action => 'new', :project_id => '1'
438 )
438 )
439 assert_recognizes(
439 assert_recognizes(
440 {:controller => 'issues', :action => 'new', :project_id => '1'},
440 {:controller => 'issues', :action => 'new', :project_id => '1'},
441 {:method => :post, :path => '/projects/1/issues'}
441 {:method => :post, :path => '/projects/1/issues'}
442 )
442 )
443 end
443 end
444
444
445 def test_show_export_to_pdf
445 def test_show_export_to_pdf
446 get :show, :id => 3, :format => 'pdf'
446 get :show, :id => 3, :format => 'pdf'
447 assert_response :success
447 assert_response :success
448 assert_equal 'application/pdf', @response.content_type
448 assert_equal 'application/pdf', @response.content_type
449 assert @response.body.starts_with?('%PDF')
449 assert @response.body.starts_with?('%PDF')
450 assert_not_nil assigns(:issue)
450 assert_not_nil assigns(:issue)
451 end
451 end
452
452
453 def test_get_new
453 def test_get_new
454 @request.session[:user_id] = 2
454 @request.session[:user_id] = 2
455 get :new, :project_id => 1, :tracker_id => 1
455 get :new, :project_id => 1, :tracker_id => 1
456 assert_response :success
456 assert_response :success
457 assert_template 'new'
457 assert_template 'new'
458
458
459 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
459 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
460 :value => 'Default string' }
460 :value => 'Default string' }
461 end
461 end
462
462
463 def test_get_new_without_tracker_id
463 def test_get_new_without_tracker_id
464 @request.session[:user_id] = 2
464 @request.session[:user_id] = 2
465 get :new, :project_id => 1
465 get :new, :project_id => 1
466 assert_response :success
466 assert_response :success
467 assert_template 'new'
467 assert_template 'new'
468
468
469 issue = assigns(:issue)
469 issue = assigns(:issue)
470 assert_not_nil issue
470 assert_not_nil issue
471 assert_equal Project.find(1).trackers.first, issue.tracker
471 assert_equal Project.find(1).trackers.first, issue.tracker
472 end
472 end
473
473
474 def test_get_new_with_no_default_status_should_display_an_error
474 def test_get_new_with_no_default_status_should_display_an_error
475 @request.session[:user_id] = 2
475 @request.session[:user_id] = 2
476 IssueStatus.delete_all
476 IssueStatus.delete_all
477
477
478 get :new, :project_id => 1
478 get :new, :project_id => 1
479 assert_response 500
479 assert_response 500
480 assert_not_nil flash[:error]
480 assert_not_nil flash[:error]
481 assert_tag :tag => 'div', :attributes => { :class => /error/ },
481 assert_tag :tag => 'div', :attributes => { :class => /error/ },
482 :content => /No default issue/
482 :content => /No default issue/
483 end
483 end
484
484
485 def test_get_new_with_no_tracker_should_display_an_error
485 def test_get_new_with_no_tracker_should_display_an_error
486 @request.session[:user_id] = 2
486 @request.session[:user_id] = 2
487 Tracker.delete_all
487 Tracker.delete_all
488
488
489 get :new, :project_id => 1
489 get :new, :project_id => 1
490 assert_response 500
490 assert_response 500
491 assert_not_nil flash[:error]
491 assert_not_nil flash[:error]
492 assert_tag :tag => 'div', :attributes => { :class => /error/ },
492 assert_tag :tag => 'div', :attributes => { :class => /error/ },
493 :content => /No tracker/
493 :content => /No tracker/
494 end
494 end
495
495
496 def test_update_new_form
496 def test_update_new_form
497 @request.session[:user_id] = 2
497 @request.session[:user_id] = 2
498 xhr :post, :update_form, :project_id => 1,
498 xhr :post, :update_form, :project_id => 1,
499 :issue => {:tracker_id => 2,
499 :issue => {:tracker_id => 2,
500 :subject => 'This is the test_new issue',
500 :subject => 'This is the test_new issue',
501 :description => 'This is the description',
501 :description => 'This is the description',
502 :priority_id => 5}
502 :priority_id => 5}
503 assert_response :success
503 assert_response :success
504 assert_template 'attributes'
504 assert_template 'attributes'
505
505
506 issue = assigns(:issue)
506 issue = assigns(:issue)
507 assert_kind_of Issue, issue
507 assert_kind_of Issue, issue
508 assert_equal 1, issue.project_id
508 assert_equal 1, issue.project_id
509 assert_equal 2, issue.tracker_id
509 assert_equal 2, issue.tracker_id
510 assert_equal 'This is the test_new issue', issue.subject
510 assert_equal 'This is the test_new issue', issue.subject
511 end
511 end
512
512
513 def test_post_new
513 def test_post_new
514 @request.session[:user_id] = 2
514 @request.session[:user_id] = 2
515 assert_difference 'Issue.count' do
515 assert_difference 'Issue.count' do
516 post :new, :project_id => 1,
516 post :new, :project_id => 1,
517 :issue => {:tracker_id => 3,
517 :issue => {:tracker_id => 3,
518 :subject => 'This is the test_new issue',
518 :subject => 'This is the test_new issue',
519 :description => 'This is the description',
519 :description => 'This is the description',
520 :priority_id => 5,
520 :priority_id => 5,
521 :estimated_hours => '',
521 :estimated_hours => '',
522 :custom_field_values => {'2' => 'Value for field 2'}}
522 :custom_field_values => {'2' => 'Value for field 2'}}
523 end
523 end
524 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
524 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
525
525
526 issue = Issue.find_by_subject('This is the test_new issue')
526 issue = Issue.find_by_subject('This is the test_new issue')
527 assert_not_nil issue
527 assert_not_nil issue
528 assert_equal 2, issue.author_id
528 assert_equal 2, issue.author_id
529 assert_equal 3, issue.tracker_id
529 assert_equal 3, issue.tracker_id
530 assert_nil issue.estimated_hours
530 assert_nil issue.estimated_hours
531 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
531 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
532 assert_not_nil v
532 assert_not_nil v
533 assert_equal 'Value for field 2', v.value
533 assert_equal 'Value for field 2', v.value
534 end
534 end
535
535
536 def test_post_new_and_continue
536 def test_post_new_and_continue
537 @request.session[:user_id] = 2
537 @request.session[:user_id] = 2
538 post :new, :project_id => 1,
538 post :new, :project_id => 1,
539 :issue => {:tracker_id => 3,
539 :issue => {:tracker_id => 3,
540 :subject => 'This is first issue',
540 :subject => 'This is first issue',
541 :priority_id => 5},
541 :priority_id => 5},
542 :continue => ''
542 :continue => ''
543 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
543 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
544 end
544 end
545
545
546 def test_post_new_without_custom_fields_param
546 def test_post_new_without_custom_fields_param
547 @request.session[:user_id] = 2
547 @request.session[:user_id] = 2
548 assert_difference 'Issue.count' do
548 assert_difference 'Issue.count' do
549 post :new, :project_id => 1,
549 post :new, :project_id => 1,
550 :issue => {:tracker_id => 1,
550 :issue => {:tracker_id => 1,
551 :subject => 'This is the test_new issue',
551 :subject => 'This is the test_new issue',
552 :description => 'This is the description',
552 :description => 'This is the description',
553 :priority_id => 5}
553 :priority_id => 5}
554 end
554 end
555 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
555 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
556 end
556 end
557
557
558 def test_post_new_with_required_custom_field_and_without_custom_fields_param
558 def test_post_new_with_required_custom_field_and_without_custom_fields_param
559 field = IssueCustomField.find_by_name('Database')
559 field = IssueCustomField.find_by_name('Database')
560 field.update_attribute(:is_required, true)
560 field.update_attribute(:is_required, true)
561
561
562 @request.session[:user_id] = 2
562 @request.session[:user_id] = 2
563 post :new, :project_id => 1,
563 post :new, :project_id => 1,
564 :issue => {:tracker_id => 1,
564 :issue => {:tracker_id => 1,
565 :subject => 'This is the test_new issue',
565 :subject => 'This is the test_new issue',
566 :description => 'This is the description',
566 :description => 'This is the description',
567 :priority_id => 5}
567 :priority_id => 5}
568 assert_response :success
568 assert_response :success
569 assert_template 'new'
569 assert_template 'new'
570 issue = assigns(:issue)
570 issue = assigns(:issue)
571 assert_not_nil issue
571 assert_not_nil issue
572 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
572 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
573 end
573 end
574
574
575 def test_post_new_with_watchers
575 def test_post_new_with_watchers
576 @request.session[:user_id] = 2
576 @request.session[:user_id] = 2
577 ActionMailer::Base.deliveries.clear
577 ActionMailer::Base.deliveries.clear
578
578
579 assert_difference 'Watcher.count', 2 do
579 assert_difference 'Watcher.count', 2 do
580 post :new, :project_id => 1,
580 post :new, :project_id => 1,
581 :issue => {:tracker_id => 1,
581 :issue => {:tracker_id => 1,
582 :subject => 'This is a new issue with watchers',
582 :subject => 'This is a new issue with watchers',
583 :description => 'This is the description',
583 :description => 'This is the description',
584 :priority_id => 5,
584 :priority_id => 5,
585 :watcher_user_ids => ['2', '3']}
585 :watcher_user_ids => ['2', '3']}
586 end
586 end
587 issue = Issue.find_by_subject('This is a new issue with watchers')
587 issue = Issue.find_by_subject('This is a new issue with watchers')
588 assert_not_nil issue
588 assert_not_nil issue
589 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
589 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
590
590
591 # Watchers added
591 # Watchers added
592 assert_equal [2, 3], issue.watcher_user_ids.sort
592 assert_equal [2, 3], issue.watcher_user_ids.sort
593 assert issue.watched_by?(User.find(3))
593 assert issue.watched_by?(User.find(3))
594 # Watchers notified
594 # Watchers notified
595 mail = ActionMailer::Base.deliveries.last
595 mail = ActionMailer::Base.deliveries.last
596 assert_kind_of TMail::Mail, mail
596 assert_kind_of TMail::Mail, mail
597 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
597 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
598 end
598 end
599
599
600 def test_post_new_should_send_a_notification
600 def test_post_new_should_send_a_notification
601 ActionMailer::Base.deliveries.clear
601 ActionMailer::Base.deliveries.clear
602 @request.session[:user_id] = 2
602 @request.session[:user_id] = 2
603 assert_difference 'Issue.count' do
603 assert_difference 'Issue.count' do
604 post :new, :project_id => 1,
604 post :new, :project_id => 1,
605 :issue => {:tracker_id => 3,
605 :issue => {:tracker_id => 3,
606 :subject => 'This is the test_new issue',
606 :subject => 'This is the test_new issue',
607 :description => 'This is the description',
607 :description => 'This is the description',
608 :priority_id => 5,
608 :priority_id => 5,
609 :estimated_hours => '',
609 :estimated_hours => '',
610 :custom_field_values => {'2' => 'Value for field 2'}}
610 :custom_field_values => {'2' => 'Value for field 2'}}
611 end
611 end
612 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
612 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
613
613
614 assert_equal 1, ActionMailer::Base.deliveries.size
614 assert_equal 1, ActionMailer::Base.deliveries.size
615 end
615 end
616
616
617 def test_post_should_preserve_fields_values_on_validation_failure
617 def test_post_should_preserve_fields_values_on_validation_failure
618 @request.session[:user_id] = 2
618 @request.session[:user_id] = 2
619 post :new, :project_id => 1,
619 post :new, :project_id => 1,
620 :issue => {:tracker_id => 1,
620 :issue => {:tracker_id => 1,
621 # empty subject
621 # empty subject
622 :subject => '',
622 :subject => '',
623 :description => 'This is a description',
623 :description => 'This is a description',
624 :priority_id => 6,
624 :priority_id => 6,
625 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
625 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
626 assert_response :success
626 assert_response :success
627 assert_template 'new'
627 assert_template 'new'
628
628
629 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
629 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
630 :content => 'This is a description'
630 :content => 'This is a description'
631 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
631 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
632 :child => { :tag => 'option', :attributes => { :selected => 'selected',
632 :child => { :tag => 'option', :attributes => { :selected => 'selected',
633 :value => '6' },
633 :value => '6' },
634 :content => 'High' }
634 :content => 'High' }
635 # Custom fields
635 # Custom fields
636 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
636 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
637 :child => { :tag => 'option', :attributes => { :selected => 'selected',
637 :child => { :tag => 'option', :attributes => { :selected => 'selected',
638 :value => 'Oracle' },
638 :value => 'Oracle' },
639 :content => 'Oracle' }
639 :content => 'Oracle' }
640 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
640 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
641 :value => 'Value for field 2'}
641 :value => 'Value for field 2'}
642 end
642 end
643
643
644 def test_copy_routing
644 def test_copy_routing
645 assert_routing(
645 assert_routing(
646 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
646 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
647 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
647 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
648 )
648 )
649 end
649 end
650
650
651 def test_copy_issue
651 def test_copy_issue
652 @request.session[:user_id] = 2
652 @request.session[:user_id] = 2
653 get :new, :project_id => 1, :copy_from => 1
653 get :new, :project_id => 1, :copy_from => 1
654 assert_template 'new'
654 assert_template 'new'
655 assert_not_nil assigns(:issue)
655 assert_not_nil assigns(:issue)
656 orig = Issue.find(1)
656 orig = Issue.find(1)
657 assert_equal orig.subject, assigns(:issue).subject
657 assert_equal orig.subject, assigns(:issue).subject
658 end
658 end
659
659
660 def test_edit_routing
660 def test_edit_routing
661 assert_routing(
661 assert_routing(
662 {:method => :get, :path => '/issues/1/edit'},
662 {:method => :get, :path => '/issues/1/edit'},
663 :controller => 'issues', :action => 'edit', :id => '1'
663 :controller => 'issues', :action => 'edit', :id => '1'
664 )
664 )
665 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
665 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
666 {:controller => 'issues', :action => 'edit', :id => '1'},
666 {:controller => 'issues', :action => 'edit', :id => '1'},
667 {:method => :post, :path => '/issues/1/edit'}
667 {:method => :post, :path => '/issues/1/edit'}
668 )
668 )
669 end
669 end
670
670
671 def test_get_edit
671 def test_get_edit
672 @request.session[:user_id] = 2
672 @request.session[:user_id] = 2
673 get :edit, :id => 1
673 get :edit, :id => 1
674 assert_response :success
674 assert_response :success
675 assert_template 'edit'
675 assert_template 'edit'
676 assert_not_nil assigns(:issue)
676 assert_not_nil assigns(:issue)
677 assert_equal Issue.find(1), assigns(:issue)
677 assert_equal Issue.find(1), assigns(:issue)
678 end
678 end
679
679
680 def test_get_edit_with_params
680 def test_get_edit_with_params
681 @request.session[:user_id] = 2
681 @request.session[:user_id] = 2
682 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
682 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
683 assert_response :success
683 assert_response :success
684 assert_template 'edit'
684 assert_template 'edit'
685
685
686 issue = assigns(:issue)
686 issue = assigns(:issue)
687 assert_not_nil issue
687 assert_not_nil issue
688
688
689 assert_equal 5, issue.status_id
689 assert_equal 5, issue.status_id
690 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
690 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
691 :child => { :tag => 'option',
691 :child => { :tag => 'option',
692 :content => 'Closed',
692 :content => 'Closed',
693 :attributes => { :selected => 'selected' } }
693 :attributes => { :selected => 'selected' } }
694
694
695 assert_equal 7, issue.priority_id
695 assert_equal 7, issue.priority_id
696 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
696 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
697 :child => { :tag => 'option',
697 :child => { :tag => 'option',
698 :content => 'Urgent',
698 :content => 'Urgent',
699 :attributes => { :selected => 'selected' } }
699 :attributes => { :selected => 'selected' } }
700 end
700 end
701
701
702 def test_update_edit_form
702 def test_update_edit_form
703 @request.session[:user_id] = 2
703 @request.session[:user_id] = 2
704 xhr :post, :update_form, :project_id => 1,
704 xhr :post, :update_form, :project_id => 1,
705 :id => 1,
705 :id => 1,
706 :issue => {:tracker_id => 2,
706 :issue => {:tracker_id => 2,
707 :subject => 'This is the test_new issue',
707 :subject => 'This is the test_new issue',
708 :description => 'This is the description',
708 :description => 'This is the description',
709 :priority_id => 5}
709 :priority_id => 5}
710 assert_response :success
710 assert_response :success
711 assert_template 'attributes'
711 assert_template 'attributes'
712
712
713 issue = assigns(:issue)
713 issue = assigns(:issue)
714 assert_kind_of Issue, issue
714 assert_kind_of Issue, issue
715 assert_equal 1, issue.id
715 assert_equal 1, issue.id
716 assert_equal 1, issue.project_id
716 assert_equal 1, issue.project_id
717 assert_equal 2, issue.tracker_id
717 assert_equal 2, issue.tracker_id
718 assert_equal 'This is the test_new issue', issue.subject
718 assert_equal 'This is the test_new issue', issue.subject
719 end
719 end
720
720
721 def test_reply_routing
721 def test_reply_routing
722 assert_routing(
722 assert_routing(
723 {:method => :post, :path => '/issues/1/quoted'},
723 {:method => :post, :path => '/issues/1/quoted'},
724 :controller => 'issues', :action => 'reply', :id => '1'
724 :controller => 'issues', :action => 'reply', :id => '1'
725 )
725 )
726 end
726 end
727
727
728 def test_reply_to_issue
728 def test_reply_to_issue
729 @request.session[:user_id] = 2
729 @request.session[:user_id] = 2
730 get :reply, :id => 1
730 get :reply, :id => 1
731 assert_response :success
731 assert_response :success
732 assert_select_rjs :show, "update"
732 assert_select_rjs :show, "update"
733 end
733 end
734
734
735 def test_reply_to_note
735 def test_reply_to_note
736 @request.session[:user_id] = 2
736 @request.session[:user_id] = 2
737 get :reply, :id => 1, :journal_id => 2
737 get :reply, :id => 1, :journal_id => 2
738 assert_response :success
738 assert_response :success
739 assert_select_rjs :show, "update"
739 assert_select_rjs :show, "update"
740 end
740 end
741
741
742 def test_post_edit_without_custom_fields_param
742 def test_post_edit_without_custom_fields_param
743 @request.session[:user_id] = 2
743 @request.session[:user_id] = 2
744 ActionMailer::Base.deliveries.clear
744 ActionMailer::Base.deliveries.clear
745
745
746 issue = Issue.find(1)
746 issue = Issue.find(1)
747 assert_equal '125', issue.custom_value_for(2).value
747 assert_equal '125', issue.custom_value_for(2).value
748 old_subject = issue.subject
748 old_subject = issue.subject
749 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
749 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
750
750
751 assert_difference('Journal.count') do
751 assert_difference('Journal.count') do
752 assert_difference('JournalDetail.count', 2) do
752 assert_difference('JournalDetail.count', 2) do
753 post :edit, :id => 1, :issue => {:subject => new_subject,
753 post :edit, :id => 1, :issue => {:subject => new_subject,
754 :priority_id => '6',
754 :priority_id => '6',
755 :category_id => '1' # no change
755 :category_id => '1' # no change
756 }
756 }
757 end
757 end
758 end
758 end
759 assert_redirected_to :action => 'show', :id => '1'
759 assert_redirected_to :action => 'show', :id => '1'
760 issue.reload
760 issue.reload
761 assert_equal new_subject, issue.subject
761 assert_equal new_subject, issue.subject
762 # Make sure custom fields were not cleared
762 # Make sure custom fields were not cleared
763 assert_equal '125', issue.custom_value_for(2).value
763 assert_equal '125', issue.custom_value_for(2).value
764
764
765 mail = ActionMailer::Base.deliveries.last
765 mail = ActionMailer::Base.deliveries.last
766 assert_kind_of TMail::Mail, mail
766 assert_kind_of TMail::Mail, mail
767 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
767 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
768 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
768 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
769 end
769 end
770
770
771 def test_post_edit_with_custom_field_change
771 def test_post_edit_with_custom_field_change
772 @request.session[:user_id] = 2
772 @request.session[:user_id] = 2
773 issue = Issue.find(1)
773 issue = Issue.find(1)
774 assert_equal '125', issue.custom_value_for(2).value
774 assert_equal '125', issue.custom_value_for(2).value
775
775
776 assert_difference('Journal.count') do
776 assert_difference('Journal.count') do
777 assert_difference('JournalDetail.count', 3) do
777 assert_difference('JournalDetail.count', 3) do
778 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
778 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
779 :priority_id => '6',
779 :priority_id => '6',
780 :category_id => '1', # no change
780 :category_id => '1', # no change
781 :custom_field_values => { '2' => 'New custom value' }
781 :custom_field_values => { '2' => 'New custom value' }
782 }
782 }
783 end
783 end
784 end
784 end
785 assert_redirected_to :action => 'show', :id => '1'
785 assert_redirected_to :action => 'show', :id => '1'
786 issue.reload
786 issue.reload
787 assert_equal 'New custom value', issue.custom_value_for(2).value
787 assert_equal 'New custom value', issue.custom_value_for(2).value
788
788
789 mail = ActionMailer::Base.deliveries.last
789 mail = ActionMailer::Base.deliveries.last
790 assert_kind_of TMail::Mail, mail
790 assert_kind_of TMail::Mail, mail
791 assert mail.body.include?("Searchable field changed from 125 to New custom value")
791 assert mail.body.include?("Searchable field changed from 125 to New custom value")
792 end
792 end
793
793
794 def test_post_edit_with_status_and_assignee_change
794 def test_post_edit_with_status_and_assignee_change
795 issue = Issue.find(1)
795 issue = Issue.find(1)
796 assert_equal 1, issue.status_id
796 assert_equal 1, issue.status_id
797 @request.session[:user_id] = 2
797 @request.session[:user_id] = 2
798 assert_difference('TimeEntry.count', 0) do
798 assert_difference('TimeEntry.count', 0) do
799 post :edit,
799 post :edit,
800 :id => 1,
800 :id => 1,
801 :issue => { :status_id => 2, :assigned_to_id => 3 },
801 :issue => { :status_id => 2, :assigned_to_id => 3 },
802 :notes => 'Assigned to dlopper',
802 :notes => 'Assigned to dlopper',
803 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
803 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
804 end
804 end
805 assert_redirected_to :action => 'show', :id => '1'
805 assert_redirected_to :action => 'show', :id => '1'
806 issue.reload
806 issue.reload
807 assert_equal 2, issue.status_id
807 assert_equal 2, issue.status_id
808 j = Journal.find(:first, :order => 'id DESC')
808 j = Journal.find(:first, :order => 'id DESC')
809 assert_equal 'Assigned to dlopper', j.notes
809 assert_equal 'Assigned to dlopper', j.notes
810 assert_equal 2, j.details.size
810 assert_equal 2, j.details.size
811
811
812 mail = ActionMailer::Base.deliveries.last
812 mail = ActionMailer::Base.deliveries.last
813 assert mail.body.include?("Status changed from New to Assigned")
813 assert mail.body.include?("Status changed from New to Assigned")
814 # subject should contain the new status
814 # subject should contain the new status
815 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
815 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
816 end
816 end
817
817
818 def test_post_edit_with_note_only
818 def test_post_edit_with_note_only
819 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
819 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
820 # anonymous user
820 # anonymous user
821 post :edit,
821 post :edit,
822 :id => 1,
822 :id => 1,
823 :notes => notes
823 :notes => notes
824 assert_redirected_to :action => 'show', :id => '1'
824 assert_redirected_to :action => 'show', :id => '1'
825 j = Journal.find(:first, :order => 'id DESC')
825 j = Journal.find(:first, :order => 'id DESC')
826 assert_equal notes, j.notes
826 assert_equal notes, j.notes
827 assert_equal 0, j.details.size
827 assert_equal 0, j.details.size
828 assert_equal User.anonymous, j.user
828 assert_equal User.anonymous, j.user
829
829
830 mail = ActionMailer::Base.deliveries.last
830 mail = ActionMailer::Base.deliveries.last
831 assert mail.body.include?(notes)
831 assert mail.body.include?(notes)
832 end
832 end
833
833
834 def test_post_edit_with_note_and_spent_time
834 def test_post_edit_with_note_and_spent_time
835 @request.session[:user_id] = 2
835 @request.session[:user_id] = 2
836 spent_hours_before = Issue.find(1).spent_hours
836 spent_hours_before = Issue.find(1).spent_hours
837 assert_difference('TimeEntry.count') do
837 assert_difference('TimeEntry.count') do
838 post :edit,
838 post :edit,
839 :id => 1,
839 :id => 1,
840 :notes => '2.5 hours added',
840 :notes => '2.5 hours added',
841 :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
841 :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
842 end
842 end
843 assert_redirected_to :action => 'show', :id => '1'
843 assert_redirected_to :action => 'show', :id => '1'
844
844
845 issue = Issue.find(1)
845 issue = Issue.find(1)
846
846
847 j = Journal.find(:first, :order => 'id DESC')
847 j = Journal.find(:first, :order => 'id DESC')
848 assert_equal '2.5 hours added', j.notes
848 assert_equal '2.5 hours added', j.notes
849 assert_equal 0, j.details.size
849 assert_equal 0, j.details.size
850
850
851 t = issue.time_entries.find(:first, :order => 'id DESC')
851 t = issue.time_entries.find(:first, :order => 'id DESC')
852 assert_not_nil t
852 assert_not_nil t
853 assert_equal 2.5, t.hours
853 assert_equal 2.5, t.hours
854 assert_equal spent_hours_before + 2.5, issue.spent_hours
854 assert_equal spent_hours_before + 2.5, issue.spent_hours
855 end
855 end
856
856
857 def test_post_edit_with_attachment_only
857 def test_post_edit_with_attachment_only
858 set_tmp_attachments_directory
858 set_tmp_attachments_directory
859
859
860 # Delete all fixtured journals, a race condition can occur causing the wrong
860 # Delete all fixtured journals, a race condition can occur causing the wrong
861 # journal to get fetched in the next find.
861 # journal to get fetched in the next find.
862 Journal.delete_all
862 Journal.delete_all
863
863
864 # anonymous user
864 # anonymous user
865 post :edit,
865 post :edit,
866 :id => 1,
866 :id => 1,
867 :notes => '',
867 :notes => '',
868 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
868 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
869 assert_redirected_to :action => 'show', :id => '1'
869 assert_redirected_to :action => 'show', :id => '1'
870 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
870 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
871 assert j.notes.blank?
871 assert j.notes.blank?
872 assert_equal 1, j.details.size
872 assert_equal 1, j.details.size
873 assert_equal 'testfile.txt', j.details.first.value
873 assert_equal 'testfile.txt', j.details.first.value
874 assert_equal User.anonymous, j.user
874 assert_equal User.anonymous, j.user
875
875
876 mail = ActionMailer::Base.deliveries.last
876 mail = ActionMailer::Base.deliveries.last
877 assert mail.body.include?('testfile.txt')
877 assert mail.body.include?('testfile.txt')
878 end
878 end
879
879
880 def test_post_edit_with_no_change
880 def test_post_edit_with_no_change
881 issue = Issue.find(1)
881 issue = Issue.find(1)
882 issue.journals.clear
882 issue.journals.clear
883 ActionMailer::Base.deliveries.clear
883 ActionMailer::Base.deliveries.clear
884
884
885 post :edit,
885 post :edit,
886 :id => 1,
886 :id => 1,
887 :notes => ''
887 :notes => ''
888 assert_redirected_to :action => 'show', :id => '1'
888 assert_redirected_to :action => 'show', :id => '1'
889
889
890 issue.reload
890 issue.reload
891 assert issue.journals.empty?
891 assert issue.journals.empty?
892 # No email should be sent
892 # No email should be sent
893 assert ActionMailer::Base.deliveries.empty?
893 assert ActionMailer::Base.deliveries.empty?
894 end
894 end
895
895
896 def test_post_edit_should_send_a_notification
896 def test_post_edit_should_send_a_notification
897 @request.session[:user_id] = 2
897 @request.session[:user_id] = 2
898 ActionMailer::Base.deliveries.clear
898 ActionMailer::Base.deliveries.clear
899 issue = Issue.find(1)
899 issue = Issue.find(1)
900 old_subject = issue.subject
900 old_subject = issue.subject
901 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
901 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
902
902
903 post :edit, :id => 1, :issue => {:subject => new_subject,
903 post :edit, :id => 1, :issue => {:subject => new_subject,
904 :priority_id => '6',
904 :priority_id => '6',
905 :category_id => '1' # no change
905 :category_id => '1' # no change
906 }
906 }
907 assert_equal 1, ActionMailer::Base.deliveries.size
907 assert_equal 1, ActionMailer::Base.deliveries.size
908 end
908 end
909
909
910 def test_post_edit_with_invalid_spent_time
910 def test_post_edit_with_invalid_spent_time
911 @request.session[:user_id] = 2
911 @request.session[:user_id] = 2
912 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
912 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
913
913
914 assert_no_difference('Journal.count') do
914 assert_no_difference('Journal.count') do
915 post :edit,
915 post :edit,
916 :id => 1,
916 :id => 1,
917 :notes => notes,
917 :notes => notes,
918 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
918 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
919 end
919 end
920 assert_response :success
920 assert_response :success
921 assert_template 'edit'
921 assert_template 'edit'
922
922
923 assert_tag :textarea, :attributes => { :name => 'notes' },
923 assert_tag :textarea, :attributes => { :name => 'notes' },
924 :content => notes
924 :content => notes
925 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
925 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
926 end
926 end
927
927
928 def test_post_edit_should_allow_fixed_version_to_be_set_to_a_subproject
928 def test_post_edit_should_allow_fixed_version_to_be_set_to_a_subproject
929 issue = Issue.find(2)
929 issue = Issue.find(2)
930 @request.session[:user_id] = 2
930 @request.session[:user_id] = 2
931
931
932 post :edit,
932 post :edit,
933 :id => issue.id,
933 :id => issue.id,
934 :issue => {
934 :issue => {
935 :fixed_version_id => 4
935 :fixed_version_id => 4
936 }
936 }
937
937
938 assert_response :redirect
938 assert_response :redirect
939 issue.reload
939 issue.reload
940 assert_equal 4, issue.fixed_version_id
940 assert_equal 4, issue.fixed_version_id
941 assert_not_equal issue.project_id, issue.fixed_version.project_id
941 assert_not_equal issue.project_id, issue.fixed_version.project_id
942 end
942 end
943
943
944 def test_get_bulk_edit
944 def test_get_bulk_edit
945 @request.session[:user_id] = 2
945 @request.session[:user_id] = 2
946 get :bulk_edit, :ids => [1, 2]
946 get :bulk_edit, :ids => [1, 2]
947 assert_response :success
947 assert_response :success
948 assert_template 'bulk_edit'
948 assert_template 'bulk_edit'
949
950 # Project specific custom field, date type
951 field = CustomField.find(9)
952 assert !field.is_for_all?
953 assert_equal 'date', field.field_format
954 assert_tag :input, :attributes => {:name => 'custom_field_values[9]'}
955
949 # System wide custom field
956 # System wide custom field
957 assert CustomField.find(1).is_for_all?
950 assert_tag :select, :attributes => {:name => 'custom_field_values[1]'}
958 assert_tag :select, :attributes => {:name => 'custom_field_values[1]'}
951 end
959 end
952
960
953 def test_bulk_edit
961 def test_bulk_edit
954 @request.session[:user_id] = 2
962 @request.session[:user_id] = 2
955 # update issues priority
963 # update issues priority
956 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
964 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
957 :assigned_to_id => '',
965 :assigned_to_id => '',
958 :custom_field_values => {'2' => ''},
966 :custom_field_values => {'2' => ''},
959 :notes => 'Bulk editing'
967 :notes => 'Bulk editing'
960 assert_response 302
968 assert_response 302
961 # check that the issues were updated
969 # check that the issues were updated
962 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
970 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
963
971
964 issue = Issue.find(1)
972 issue = Issue.find(1)
965 journal = issue.journals.find(:first, :order => 'created_on DESC')
973 journal = issue.journals.find(:first, :order => 'created_on DESC')
966 assert_equal '125', issue.custom_value_for(2).value
974 assert_equal '125', issue.custom_value_for(2).value
967 assert_equal 'Bulk editing', journal.notes
975 assert_equal 'Bulk editing', journal.notes
968 assert_equal 1, journal.details.size
976 assert_equal 1, journal.details.size
969 end
977 end
970
978
971 def test_bullk_edit_should_send_a_notification
979 def test_bullk_edit_should_send_a_notification
972 @request.session[:user_id] = 2
980 @request.session[:user_id] = 2
973 ActionMailer::Base.deliveries.clear
981 ActionMailer::Base.deliveries.clear
974 post(:bulk_edit,
982 post(:bulk_edit,
975 {
983 {
976 :ids => [1, 2],
984 :ids => [1, 2],
977 :priority_id => 7,
985 :priority_id => 7,
978 :assigned_to_id => '',
986 :assigned_to_id => '',
979 :custom_field_values => {'2' => ''},
987 :custom_field_values => {'2' => ''},
980 :notes => 'Bulk editing'
988 :notes => 'Bulk editing'
981 })
989 })
982
990
983 assert_response 302
991 assert_response 302
984 assert_equal 2, ActionMailer::Base.deliveries.size
992 assert_equal 2, ActionMailer::Base.deliveries.size
985 end
993 end
986
994
987 def test_bulk_edit_status
995 def test_bulk_edit_status
988 @request.session[:user_id] = 2
996 @request.session[:user_id] = 2
989 # update issues priority
997 # update issues priority
990 post :bulk_edit, :ids => [1, 2], :priority_id => '',
998 post :bulk_edit, :ids => [1, 2], :priority_id => '',
991 :assigned_to_id => '',
999 :assigned_to_id => '',
992 :status_id => '5',
1000 :status_id => '5',
993 :notes => 'Bulk editing status'
1001 :notes => 'Bulk editing status'
994 assert_response 302
1002 assert_response 302
995 issue = Issue.find(1)
1003 issue = Issue.find(1)
996 assert issue.closed?
1004 assert issue.closed?
997 end
1005 end
998
1006
999 def test_bulk_edit_custom_field
1007 def test_bulk_edit_custom_field
1000 @request.session[:user_id] = 2
1008 @request.session[:user_id] = 2
1001 # update issues priority
1009 # update issues priority
1002 post :bulk_edit, :ids => [1, 2], :priority_id => '',
1010 post :bulk_edit, :ids => [1, 2], :priority_id => '',
1003 :assigned_to_id => '',
1011 :assigned_to_id => '',
1004 :custom_field_values => {'2' => '777'},
1012 :custom_field_values => {'2' => '777'},
1005 :notes => 'Bulk editing custom field'
1013 :notes => 'Bulk editing custom field'
1006 assert_response 302
1014 assert_response 302
1007
1015
1008 issue = Issue.find(1)
1016 issue = Issue.find(1)
1009 journal = issue.journals.find(:first, :order => 'created_on DESC')
1017 journal = issue.journals.find(:first, :order => 'created_on DESC')
1010 assert_equal '777', issue.custom_value_for(2).value
1018 assert_equal '777', issue.custom_value_for(2).value
1011 assert_equal 1, journal.details.size
1019 assert_equal 1, journal.details.size
1012 assert_equal '125', journal.details.first.old_value
1020 assert_equal '125', journal.details.first.old_value
1013 assert_equal '777', journal.details.first.value
1021 assert_equal '777', journal.details.first.value
1014 end
1022 end
1015
1023
1016 def test_bulk_unassign
1024 def test_bulk_unassign
1017 assert_not_nil Issue.find(2).assigned_to
1025 assert_not_nil Issue.find(2).assigned_to
1018 @request.session[:user_id] = 2
1026 @request.session[:user_id] = 2
1019 # unassign issues
1027 # unassign issues
1020 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
1028 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
1021 assert_response 302
1029 assert_response 302
1022 # check that the issues were updated
1030 # check that the issues were updated
1023 assert_nil Issue.find(2).assigned_to
1031 assert_nil Issue.find(2).assigned_to
1024 end
1032 end
1025
1033
1026 def test_post_bulk_edit_should_allow_fixed_version_to_be_set_to_a_subproject
1034 def test_post_bulk_edit_should_allow_fixed_version_to_be_set_to_a_subproject
1027 @request.session[:user_id] = 2
1035 @request.session[:user_id] = 2
1028
1036
1029 post :bulk_edit,
1037 post :bulk_edit,
1030 :ids => [1,2],
1038 :ids => [1,2],
1031 :fixed_version_id => 4
1039 :fixed_version_id => 4
1032
1040
1033 assert_response :redirect
1041 assert_response :redirect
1034 issues = Issue.find([1,2])
1042 issues = Issue.find([1,2])
1035 issues.each do |issue|
1043 issues.each do |issue|
1036 assert_equal 4, issue.fixed_version_id
1044 assert_equal 4, issue.fixed_version_id
1037 assert_not_equal issue.project_id, issue.fixed_version.project_id
1045 assert_not_equal issue.project_id, issue.fixed_version.project_id
1038 end
1046 end
1039 end
1047 end
1040
1048
1041 def test_move_routing
1049 def test_move_routing
1042 assert_routing(
1050 assert_routing(
1043 {:method => :get, :path => '/issues/1/move'},
1051 {:method => :get, :path => '/issues/1/move'},
1044 :controller => 'issues', :action => 'move', :id => '1'
1052 :controller => 'issues', :action => 'move', :id => '1'
1045 )
1053 )
1046 assert_recognizes(
1054 assert_recognizes(
1047 {:controller => 'issues', :action => 'move', :id => '1'},
1055 {:controller => 'issues', :action => 'move', :id => '1'},
1048 {:method => :post, :path => '/issues/1/move'}
1056 {:method => :post, :path => '/issues/1/move'}
1049 )
1057 )
1050 end
1058 end
1051
1059
1052 def test_move_one_issue_to_another_project
1060 def test_move_one_issue_to_another_project
1053 @request.session[:user_id] = 2
1061 @request.session[:user_id] = 2
1054 post :move, :id => 1, :new_project_id => 2, :tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1062 post :move, :id => 1, :new_project_id => 2, :tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1055 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1063 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1056 assert_equal 2, Issue.find(1).project_id
1064 assert_equal 2, Issue.find(1).project_id
1057 end
1065 end
1058
1066
1059 def test_move_one_issue_to_another_project_should_follow_when_needed
1067 def test_move_one_issue_to_another_project_should_follow_when_needed
1060 @request.session[:user_id] = 2
1068 @request.session[:user_id] = 2
1061 post :move, :id => 1, :new_project_id => 2, :follow => '1'
1069 post :move, :id => 1, :new_project_id => 2, :follow => '1'
1062 assert_redirected_to '/issues/1'
1070 assert_redirected_to '/issues/1'
1063 end
1071 end
1064
1072
1065 def test_bulk_move_to_another_project
1073 def test_bulk_move_to_another_project
1066 @request.session[:user_id] = 2
1074 @request.session[:user_id] = 2
1067 post :move, :ids => [1, 2], :new_project_id => 2
1075 post :move, :ids => [1, 2], :new_project_id => 2
1068 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1076 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1069 # Issues moved to project 2
1077 # Issues moved to project 2
1070 assert_equal 2, Issue.find(1).project_id
1078 assert_equal 2, Issue.find(1).project_id
1071 assert_equal 2, Issue.find(2).project_id
1079 assert_equal 2, Issue.find(2).project_id
1072 # No tracker change
1080 # No tracker change
1073 assert_equal 1, Issue.find(1).tracker_id
1081 assert_equal 1, Issue.find(1).tracker_id
1074 assert_equal 2, Issue.find(2).tracker_id
1082 assert_equal 2, Issue.find(2).tracker_id
1075 end
1083 end
1076
1084
1077 def test_bulk_move_to_another_tracker
1085 def test_bulk_move_to_another_tracker
1078 @request.session[:user_id] = 2
1086 @request.session[:user_id] = 2
1079 post :move, :ids => [1, 2], :new_tracker_id => 2
1087 post :move, :ids => [1, 2], :new_tracker_id => 2
1080 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1088 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1081 assert_equal 2, Issue.find(1).tracker_id
1089 assert_equal 2, Issue.find(1).tracker_id
1082 assert_equal 2, Issue.find(2).tracker_id
1090 assert_equal 2, Issue.find(2).tracker_id
1083 end
1091 end
1084
1092
1085 def test_bulk_copy_to_another_project
1093 def test_bulk_copy_to_another_project
1086 @request.session[:user_id] = 2
1094 @request.session[:user_id] = 2
1087 assert_difference 'Issue.count', 2 do
1095 assert_difference 'Issue.count', 2 do
1088 assert_no_difference 'Project.find(1).issues.count' do
1096 assert_no_difference 'Project.find(1).issues.count' do
1089 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
1097 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
1090 end
1098 end
1091 end
1099 end
1092 assert_redirected_to 'projects/ecookbook/issues'
1100 assert_redirected_to 'projects/ecookbook/issues'
1093 end
1101 end
1094
1102
1095 context "#move via bulk copy" do
1103 context "#move via bulk copy" do
1096 should "allow not changing the issue's attributes" do
1104 should "allow not changing the issue's attributes" do
1097 @request.session[:user_id] = 2
1105 @request.session[:user_id] = 2
1098 issue_before_move = Issue.find(1)
1106 issue_before_move = Issue.find(1)
1099 assert_difference 'Issue.count', 1 do
1107 assert_difference 'Issue.count', 1 do
1100 assert_no_difference 'Project.find(1).issues.count' do
1108 assert_no_difference 'Project.find(1).issues.count' do
1101 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :new_tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1109 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :new_tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1102 end
1110 end
1103 end
1111 end
1104 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
1112 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
1105 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
1113 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
1106 assert_equal issue_before_move.status_id, issue_after_move.status_id
1114 assert_equal issue_before_move.status_id, issue_after_move.status_id
1107 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
1115 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
1108 end
1116 end
1109
1117
1110 should "allow changing the issue's attributes" do
1118 should "allow changing the issue's attributes" do
1111 @request.session[:user_id] = 2
1119 @request.session[:user_id] = 2
1112 assert_difference 'Issue.count', 2 do
1120 assert_difference 'Issue.count', 2 do
1113 assert_no_difference 'Project.find(1).issues.count' do
1121 assert_no_difference 'Project.find(1).issues.count' do
1114 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}, :new_tracker_id => '', :assigned_to_id => 4, :status_id => 3, :start_date => '2009-12-01', :due_date => '2009-12-31'
1122 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}, :new_tracker_id => '', :assigned_to_id => 4, :status_id => 3, :start_date => '2009-12-01', :due_date => '2009-12-31'
1115 end
1123 end
1116 end
1124 end
1117
1125
1118 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
1126 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
1119 assert_equal 2, copied_issues.size
1127 assert_equal 2, copied_issues.size
1120 copied_issues.each do |issue|
1128 copied_issues.each do |issue|
1121 assert_equal 2, issue.project_id, "Project is incorrect"
1129 assert_equal 2, issue.project_id, "Project is incorrect"
1122 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
1130 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
1123 assert_equal 3, issue.status_id, "Status is incorrect"
1131 assert_equal 3, issue.status_id, "Status is incorrect"
1124 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
1132 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
1125 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
1133 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
1126 end
1134 end
1127 end
1135 end
1128 end
1136 end
1129
1137
1130 def test_copy_to_another_project_should_follow_when_needed
1138 def test_copy_to_another_project_should_follow_when_needed
1131 @request.session[:user_id] = 2
1139 @request.session[:user_id] = 2
1132 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :follow => '1'
1140 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :follow => '1'
1133 issue = Issue.first(:order => 'id DESC')
1141 issue = Issue.first(:order => 'id DESC')
1134 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1142 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1135 end
1143 end
1136
1144
1137 def test_context_menu_one_issue
1145 def test_context_menu_one_issue
1138 @request.session[:user_id] = 2
1146 @request.session[:user_id] = 2
1139 get :context_menu, :ids => [1]
1147 get :context_menu, :ids => [1]
1140 assert_response :success
1148 assert_response :success
1141 assert_template 'context_menu'
1149 assert_template 'context_menu'
1142 assert_tag :tag => 'a', :content => 'Edit',
1150 assert_tag :tag => 'a', :content => 'Edit',
1143 :attributes => { :href => '/issues/1/edit',
1151 :attributes => { :href => '/issues/1/edit',
1144 :class => 'icon-edit' }
1152 :class => 'icon-edit' }
1145 assert_tag :tag => 'a', :content => 'Closed',
1153 assert_tag :tag => 'a', :content => 'Closed',
1146 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
1154 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
1147 :class => '' }
1155 :class => '' }
1148 assert_tag :tag => 'a', :content => 'Immediate',
1156 assert_tag :tag => 'a', :content => 'Immediate',
1149 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1157 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1150 :class => '' }
1158 :class => '' }
1151 # Versions
1159 # Versions
1152 assert_tag :tag => 'a', :content => '2.0',
1160 assert_tag :tag => 'a', :content => '2.0',
1153 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=3&amp;ids%5B%5D=1',
1161 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=3&amp;ids%5B%5D=1',
1154 :class => '' }
1162 :class => '' }
1155 assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0',
1163 assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0',
1156 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=4&amp;ids%5B%5D=1',
1164 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=4&amp;ids%5B%5D=1',
1157 :class => '' }
1165 :class => '' }
1158
1166
1159 assert_tag :tag => 'a', :content => 'Dave Lopper',
1167 assert_tag :tag => 'a', :content => 'Dave Lopper',
1160 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1168 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1161 :class => '' }
1169 :class => '' }
1162 assert_tag :tag => 'a', :content => 'Duplicate',
1170 assert_tag :tag => 'a', :content => 'Duplicate',
1163 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
1171 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
1164 :class => 'icon-duplicate' }
1172 :class => 'icon-duplicate' }
1165 assert_tag :tag => 'a', :content => 'Copy',
1173 assert_tag :tag => 'a', :content => 'Copy',
1166 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1',
1174 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1',
1167 :class => 'icon-copy' }
1175 :class => 'icon-copy' }
1168 assert_tag :tag => 'a', :content => 'Move',
1176 assert_tag :tag => 'a', :content => 'Move',
1169 :attributes => { :href => '/issues/move?ids%5B%5D=1',
1177 :attributes => { :href => '/issues/move?ids%5B%5D=1',
1170 :class => 'icon-move' }
1178 :class => 'icon-move' }
1171 assert_tag :tag => 'a', :content => 'Delete',
1179 assert_tag :tag => 'a', :content => 'Delete',
1172 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
1180 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
1173 :class => 'icon-del' }
1181 :class => 'icon-del' }
1174 end
1182 end
1175
1183
1176 def test_context_menu_one_issue_by_anonymous
1184 def test_context_menu_one_issue_by_anonymous
1177 get :context_menu, :ids => [1]
1185 get :context_menu, :ids => [1]
1178 assert_response :success
1186 assert_response :success
1179 assert_template 'context_menu'
1187 assert_template 'context_menu'
1180 assert_tag :tag => 'a', :content => 'Delete',
1188 assert_tag :tag => 'a', :content => 'Delete',
1181 :attributes => { :href => '#',
1189 :attributes => { :href => '#',
1182 :class => 'icon-del disabled' }
1190 :class => 'icon-del disabled' }
1183 end
1191 end
1184
1192
1185 def test_context_menu_multiple_issues_of_same_project
1193 def test_context_menu_multiple_issues_of_same_project
1186 @request.session[:user_id] = 2
1194 @request.session[:user_id] = 2
1187 get :context_menu, :ids => [1, 2]
1195 get :context_menu, :ids => [1, 2]
1188 assert_response :success
1196 assert_response :success
1189 assert_template 'context_menu'
1197 assert_template 'context_menu'
1190 assert_tag :tag => 'a', :content => 'Edit',
1198 assert_tag :tag => 'a', :content => 'Edit',
1191 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
1199 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
1192 :class => 'icon-edit' }
1200 :class => 'icon-edit' }
1193 assert_tag :tag => 'a', :content => 'Immediate',
1201 assert_tag :tag => 'a', :content => 'Immediate',
1194 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
1202 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
1195 :class => '' }
1203 :class => '' }
1196 assert_tag :tag => 'a', :content => 'Dave Lopper',
1204 assert_tag :tag => 'a', :content => 'Dave Lopper',
1197 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1205 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1198 :class => '' }
1206 :class => '' }
1199 assert_tag :tag => 'a', :content => 'Copy',
1207 assert_tag :tag => 'a', :content => 'Copy',
1200 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1208 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1201 :class => 'icon-copy' }
1209 :class => 'icon-copy' }
1202 assert_tag :tag => 'a', :content => 'Move',
1210 assert_tag :tag => 'a', :content => 'Move',
1203 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
1211 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
1204 :class => 'icon-move' }
1212 :class => 'icon-move' }
1205 assert_tag :tag => 'a', :content => 'Delete',
1213 assert_tag :tag => 'a', :content => 'Delete',
1206 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
1214 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
1207 :class => 'icon-del' }
1215 :class => 'icon-del' }
1208 end
1216 end
1209
1217
1210 def test_context_menu_multiple_issues_of_different_project
1218 def test_context_menu_multiple_issues_of_different_project
1211 @request.session[:user_id] = 2
1219 @request.session[:user_id] = 2
1212 get :context_menu, :ids => [1, 2, 4]
1220 get :context_menu, :ids => [1, 2, 4]
1213 assert_response :success
1221 assert_response :success
1214 assert_template 'context_menu'
1222 assert_template 'context_menu'
1215 assert_tag :tag => 'a', :content => 'Delete',
1223 assert_tag :tag => 'a', :content => 'Delete',
1216 :attributes => { :href => '#',
1224 :attributes => { :href => '#',
1217 :class => 'icon-del disabled' }
1225 :class => 'icon-del disabled' }
1218 end
1226 end
1219
1227
1220 def test_destroy_routing
1228 def test_destroy_routing
1221 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
1229 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
1222 {:controller => 'issues', :action => 'destroy', :id => '1'},
1230 {:controller => 'issues', :action => 'destroy', :id => '1'},
1223 {:method => :post, :path => '/issues/1/destroy'}
1231 {:method => :post, :path => '/issues/1/destroy'}
1224 )
1232 )
1225 end
1233 end
1226
1234
1227 def test_destroy_issue_with_no_time_entries
1235 def test_destroy_issue_with_no_time_entries
1228 assert_nil TimeEntry.find_by_issue_id(2)
1236 assert_nil TimeEntry.find_by_issue_id(2)
1229 @request.session[:user_id] = 2
1237 @request.session[:user_id] = 2
1230 post :destroy, :id => 2
1238 post :destroy, :id => 2
1231 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1239 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1232 assert_nil Issue.find_by_id(2)
1240 assert_nil Issue.find_by_id(2)
1233 end
1241 end
1234
1242
1235 def test_destroy_issues_with_time_entries
1243 def test_destroy_issues_with_time_entries
1236 @request.session[:user_id] = 2
1244 @request.session[:user_id] = 2
1237 post :destroy, :ids => [1, 3]
1245 post :destroy, :ids => [1, 3]
1238 assert_response :success
1246 assert_response :success
1239 assert_template 'destroy'
1247 assert_template 'destroy'
1240 assert_not_nil assigns(:hours)
1248 assert_not_nil assigns(:hours)
1241 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1249 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1242 end
1250 end
1243
1251
1244 def test_destroy_issues_and_destroy_time_entries
1252 def test_destroy_issues_and_destroy_time_entries
1245 @request.session[:user_id] = 2
1253 @request.session[:user_id] = 2
1246 post :destroy, :ids => [1, 3], :todo => 'destroy'
1254 post :destroy, :ids => [1, 3], :todo => 'destroy'
1247 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1255 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1248 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1256 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1249 assert_nil TimeEntry.find_by_id([1, 2])
1257 assert_nil TimeEntry.find_by_id([1, 2])
1250 end
1258 end
1251
1259
1252 def test_destroy_issues_and_assign_time_entries_to_project
1260 def test_destroy_issues_and_assign_time_entries_to_project
1253 @request.session[:user_id] = 2
1261 @request.session[:user_id] = 2
1254 post :destroy, :ids => [1, 3], :todo => 'nullify'
1262 post :destroy, :ids => [1, 3], :todo => 'nullify'
1255 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1263 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1256 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1264 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1257 assert_nil TimeEntry.find(1).issue_id
1265 assert_nil TimeEntry.find(1).issue_id
1258 assert_nil TimeEntry.find(2).issue_id
1266 assert_nil TimeEntry.find(2).issue_id
1259 end
1267 end
1260
1268
1261 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1269 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1262 @request.session[:user_id] = 2
1270 @request.session[:user_id] = 2
1263 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1271 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1264 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1272 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1265 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1273 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1266 assert_equal 2, TimeEntry.find(1).issue_id
1274 assert_equal 2, TimeEntry.find(1).issue_id
1267 assert_equal 2, TimeEntry.find(2).issue_id
1275 assert_equal 2, TimeEntry.find(2).issue_id
1268 end
1276 end
1269
1277
1270 def test_default_search_scope
1278 def test_default_search_scope
1271 get :index
1279 get :index
1272 assert_tag :div, :attributes => {:id => 'quick-search'},
1280 assert_tag :div, :attributes => {:id => 'quick-search'},
1273 :child => {:tag => 'form',
1281 :child => {:tag => 'form',
1274 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1282 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1275 end
1283 end
1276 end
1284 end
General Comments 0
You need to be logged in to leave comments. Login now