##// END OF EJS Templates
Refactor: Start to extract IssuesController#edit into #update (REST)....
Eric Davis -
r3366:d581510af605
parent child
Show More
@@ -1,562 +1,569
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, :update, :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 include QueriesHelper
43 include QueriesHelper
44 helper :sort
44 helper :sort
45 include SortHelper
45 include SortHelper
46 include IssuesHelper
46 include IssuesHelper
47 helper :timelog
47 helper :timelog
48 include Redmine::Export::PDF
48 include Redmine::Export::PDF
49
49
50 verify :method => [:post, :delete],
50 verify :method => [:post, :delete],
51 :only => :destroy,
51 :only => :destroy,
52 :render => { :nothing => true, :status => :method_not_allowed }
52 :render => { :nothing => true, :status => :method_not_allowed }
53
53
54 def index
54 def index
55 retrieve_query
55 retrieve_query
56 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
56 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
57 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
57 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
58
58
59 if @query.valid?
59 if @query.valid?
60 limit = case params[:format]
60 limit = case params[:format]
61 when 'csv', 'pdf'
61 when 'csv', 'pdf'
62 Setting.issues_export_limit.to_i
62 Setting.issues_export_limit.to_i
63 when 'atom'
63 when 'atom'
64 Setting.feeds_limit.to_i
64 Setting.feeds_limit.to_i
65 else
65 else
66 per_page_option
66 per_page_option
67 end
67 end
68
68
69 @issue_count = @query.issue_count
69 @issue_count = @query.issue_count
70 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
70 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
71 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
71 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
72 :order => sort_clause,
72 :order => sort_clause,
73 :offset => @issue_pages.current.offset,
73 :offset => @issue_pages.current.offset,
74 :limit => limit)
74 :limit => limit)
75 @issue_count_by_group = @query.issue_count_by_group
75 @issue_count_by_group = @query.issue_count_by_group
76
76
77 respond_to do |format|
77 respond_to do |format|
78 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
78 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
79 format.xml { render :layout => false }
79 format.xml { render :layout => false }
80 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
80 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
81 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
81 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
82 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
82 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
83 end
83 end
84 else
84 else
85 # Send html if the query is not valid
85 # Send html if the query is not valid
86 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
86 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
87 end
87 end
88 rescue ActiveRecord::RecordNotFound
88 rescue ActiveRecord::RecordNotFound
89 render_404
89 render_404
90 end
90 end
91
91
92 def changes
92 def changes
93 retrieve_query
93 retrieve_query
94 sort_init 'id', 'desc'
94 sort_init 'id', 'desc'
95 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
95 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
96
96
97 if @query.valid?
97 if @query.valid?
98 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
98 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
99 :limit => 25)
99 :limit => 25)
100 end
100 end
101 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
101 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
102 render :layout => false, :content_type => 'application/atom+xml'
102 render :layout => false, :content_type => 'application/atom+xml'
103 rescue ActiveRecord::RecordNotFound
103 rescue ActiveRecord::RecordNotFound
104 render_404
104 render_404
105 end
105 end
106
106
107 def show
107 def show
108 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
108 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
109 @journals.each_with_index {|j,i| j.indice = i+1}
109 @journals.each_with_index {|j,i| j.indice = i+1}
110 @journals.reverse! if User.current.wants_comments_in_reverse_order?
110 @journals.reverse! if User.current.wants_comments_in_reverse_order?
111 @changesets = @issue.changesets.visible.all
111 @changesets = @issue.changesets.visible.all
112 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
112 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
113 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
113 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
114 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
114 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
115 @priorities = IssuePriority.all
115 @priorities = IssuePriority.all
116 @time_entry = TimeEntry.new
116 @time_entry = TimeEntry.new
117 respond_to do |format|
117 respond_to do |format|
118 format.html { render :template => 'issues/show.rhtml' }
118 format.html { render :template => 'issues/show.rhtml' }
119 format.xml { render :layout => false }
119 format.xml { render :layout => false }
120 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
120 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
121 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
121 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
122 end
122 end
123 end
123 end
124
124
125 # Add a new issue
125 # Add a new issue
126 # The new issue will be created from an existing one if copy_from parameter is given
126 # The new issue will be created from an existing one if copy_from parameter is given
127 def new
127 def new
128 @issue = Issue.new
128 @issue = Issue.new
129 @issue.copy_from(params[:copy_from]) if params[:copy_from]
129 @issue.copy_from(params[:copy_from]) if params[:copy_from]
130 @issue.project = @project
130 @issue.project = @project
131 # Tracker must be set before custom field values
131 # Tracker must be set before custom field values
132 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
132 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
133 if @issue.tracker.nil?
133 if @issue.tracker.nil?
134 render_error l(:error_no_tracker_in_project)
134 render_error l(:error_no_tracker_in_project)
135 return
135 return
136 end
136 end
137 if params[:issue].is_a?(Hash)
137 if params[:issue].is_a?(Hash)
138 @issue.safe_attributes = params[:issue]
138 @issue.safe_attributes = params[:issue]
139 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
139 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
140 end
140 end
141 @issue.author = User.current
141 @issue.author = User.current
142
142
143 default_status = IssueStatus.default
143 default_status = IssueStatus.default
144 unless default_status
144 unless default_status
145 render_error l(:error_no_default_issue_status)
145 render_error l(:error_no_default_issue_status)
146 return
146 return
147 end
147 end
148 @issue.status = default_status
148 @issue.status = default_status
149 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
149 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
150
150
151 if request.get? || request.xhr?
151 if request.get? || request.xhr?
152 @issue.start_date ||= Date.today
152 @issue.start_date ||= Date.today
153 else
153 else
154 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
154 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
155 # Check that the user is allowed to apply the requested status
155 # Check that the user is allowed to apply the requested status
156 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
156 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
157 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
157 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
158 if @issue.save
158 if @issue.save
159 attach_files(@issue, params[:attachments])
159 attach_files(@issue, params[:attachments])
160 flash[:notice] = l(:notice_successful_create)
160 flash[:notice] = l(:notice_successful_create)
161 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
161 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
162 respond_to do |format|
162 respond_to do |format|
163 format.html {
163 format.html {
164 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
164 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
165 { :action => 'show', :id => @issue })
165 { :action => 'show', :id => @issue })
166 }
166 }
167 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
167 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
168 end
168 end
169 return
169 return
170 else
170 else
171 respond_to do |format|
171 respond_to do |format|
172 format.html { }
172 format.html { }
173 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
173 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
174 end
174 end
175 end
175 end
176 end
176 end
177 @priorities = IssuePriority.all
177 @priorities = IssuePriority.all
178 render :layout => !request.xhr?
178 render :layout => !request.xhr?
179 end
179 end
180
180
181 # Attributes that can be updated on workflow transition (without :edit permission)
181 # Attributes that can be updated on workflow transition (without :edit permission)
182 # TODO: make it configurable (at least per role)
182 # TODO: make it configurable (at least per role)
183 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
183 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
184
184
185 def edit
185 def edit
186 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
186 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
187 @priorities = IssuePriority.all
187 @priorities = IssuePriority.all
188 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
188 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
189 @time_entry = TimeEntry.new
189 @time_entry = TimeEntry.new
190
190
191 @notes = params[:notes]
191 @notes = params[:notes]
192 journal = @issue.init_journal(User.current, @notes)
192 journal = @issue.init_journal(User.current, @notes)
193 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
193 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
194 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
194 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
195 attrs = params[:issue].dup
195 attrs = params[:issue].dup
196 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
196 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
197 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
197 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
198 @issue.safe_attributes = attrs
198 @issue.safe_attributes = attrs
199 end
199 end
200
200
201 if request.get?
201 if request.get?
202 # nop
202 # nop
203 else
203 else
204 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
204 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
205 @time_entry.attributes = params[:time_entry]
205 @time_entry.attributes = params[:time_entry]
206 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.valid?
206 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.valid?
207 attachments = attach_files(@issue, params[:attachments])
207 attachments = attach_files(@issue, params[:attachments])
208 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
208 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
209 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
209 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
210 if @issue.save
210 if @issue.save
211 # Log spend time
211 # Log spend time
212 if User.current.allowed_to?(:log_time, @project)
212 if User.current.allowed_to?(:log_time, @project)
213 @time_entry.save
213 @time_entry.save
214 end
214 end
215 if !journal.new_record?
215 if !journal.new_record?
216 # Only send notification if something was actually changed
216 # Only send notification if something was actually changed
217 flash[:notice] = l(:notice_successful_update)
217 flash[:notice] = l(:notice_successful_update)
218 end
218 end
219 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
219 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
220 respond_to do |format|
220 respond_to do |format|
221 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
221 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
222 format.xml { head :ok }
222 format.xml { head :ok }
223 end
223 end
224 return
224 return
225 end
225 end
226 end
226 end
227 # failure
227 # failure
228 respond_to do |format|
228 respond_to do |format|
229 format.html { }
229 format.html { render :action => 'edit' }
230 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
230 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
231 end
231 end
232 end
232 end
233 rescue ActiveRecord::StaleObjectError
233 rescue ActiveRecord::StaleObjectError
234 # Optimistic locking exception
234 # Optimistic locking exception
235 flash.now[:error] = l(:notice_locking_conflict)
235 flash.now[:error] = l(:notice_locking_conflict)
236 # Remove the previously added attachments if issue was not updated
236 # Remove the previously added attachments if issue was not updated
237 attachments.each(&:destroy)
237 attachments.each(&:destroy)
238 end
238 end
239
239
240 #--
241 # Start converting to the Rails REST controllers
242 #++
243 def update
244 edit
245 end
246
240 def reply
247 def reply
241 journal = Journal.find(params[:journal_id]) if params[:journal_id]
248 journal = Journal.find(params[:journal_id]) if params[:journal_id]
242 if journal
249 if journal
243 user = journal.user
250 user = journal.user
244 text = journal.notes
251 text = journal.notes
245 else
252 else
246 user = @issue.author
253 user = @issue.author
247 text = @issue.description
254 text = @issue.description
248 end
255 end
249 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
256 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
250 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
257 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
251 render(:update) { |page|
258 render(:update) { |page|
252 page.<< "$('notes').value = \"#{content}\";"
259 page.<< "$('notes').value = \"#{content}\";"
253 page.show 'update'
260 page.show 'update'
254 page << "Form.Element.focus('notes');"
261 page << "Form.Element.focus('notes');"
255 page << "Element.scrollTo('update');"
262 page << "Element.scrollTo('update');"
256 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
263 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
257 }
264 }
258 end
265 end
259
266
260 # Bulk edit a set of issues
267 # Bulk edit a set of issues
261 def bulk_edit
268 def bulk_edit
262 if request.post?
269 if request.post?
263 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
270 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
264 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
271 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
265 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
272 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
266
273
267 unsaved_issue_ids = []
274 unsaved_issue_ids = []
268 @issues.each do |issue|
275 @issues.each do |issue|
269 journal = issue.init_journal(User.current, params[:notes])
276 journal = issue.init_journal(User.current, params[:notes])
270 issue.safe_attributes = attributes
277 issue.safe_attributes = attributes
271 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
278 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
272 unless issue.save
279 unless issue.save
273 # Keep unsaved issue ids to display them in flash error
280 # Keep unsaved issue ids to display them in flash error
274 unsaved_issue_ids << issue.id
281 unsaved_issue_ids << issue.id
275 end
282 end
276 end
283 end
277 if unsaved_issue_ids.empty?
284 if unsaved_issue_ids.empty?
278 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
285 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
279 else
286 else
280 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
287 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
281 :total => @issues.size,
288 :total => @issues.size,
282 :ids => '#' + unsaved_issue_ids.join(', #'))
289 :ids => '#' + unsaved_issue_ids.join(', #'))
283 end
290 end
284 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
291 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
285 return
292 return
286 end
293 end
287 @available_statuses = Workflow.available_statuses(@project)
294 @available_statuses = Workflow.available_statuses(@project)
288 @custom_fields = @project.all_issue_custom_fields
295 @custom_fields = @project.all_issue_custom_fields
289 end
296 end
290
297
291 def move
298 def move
292 @copy = params[:copy_options] && params[:copy_options][:copy]
299 @copy = params[:copy_options] && params[:copy_options][:copy]
293 @allowed_projects = []
300 @allowed_projects = []
294 # find projects to which the user is allowed to move the issue
301 # find projects to which the user is allowed to move the issue
295 if User.current.admin?
302 if User.current.admin?
296 # admin is allowed to move issues to any active (visible) project
303 # admin is allowed to move issues to any active (visible) project
297 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
304 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
298 else
305 else
299 User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
306 User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
300 end
307 end
301 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
308 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
302 @target_project ||= @project
309 @target_project ||= @project
303 @trackers = @target_project.trackers
310 @trackers = @target_project.trackers
304 @available_statuses = Workflow.available_statuses(@project)
311 @available_statuses = Workflow.available_statuses(@project)
305 if request.post?
312 if request.post?
306 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
313 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
307 unsaved_issue_ids = []
314 unsaved_issue_ids = []
308 moved_issues = []
315 moved_issues = []
309 @issues.each do |issue|
316 @issues.each do |issue|
310 changed_attributes = {}
317 changed_attributes = {}
311 [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
318 [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
312 unless params[valid_attribute].blank?
319 unless params[valid_attribute].blank?
313 changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
320 changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
314 end
321 end
315 end
322 end
316 issue.init_journal(User.current)
323 issue.init_journal(User.current)
317 call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
324 call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
318 if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
325 if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
319 moved_issues << r
326 moved_issues << r
320 else
327 else
321 unsaved_issue_ids << issue.id
328 unsaved_issue_ids << issue.id
322 end
329 end
323 end
330 end
324 if unsaved_issue_ids.empty?
331 if unsaved_issue_ids.empty?
325 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
332 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
326 else
333 else
327 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
334 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
328 :total => @issues.size,
335 :total => @issues.size,
329 :ids => '#' + unsaved_issue_ids.join(', #'))
336 :ids => '#' + unsaved_issue_ids.join(', #'))
330 end
337 end
331 if params[:follow]
338 if params[:follow]
332 if @issues.size == 1 && moved_issues.size == 1
339 if @issues.size == 1 && moved_issues.size == 1
333 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
340 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
334 else
341 else
335 redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
342 redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
336 end
343 end
337 else
344 else
338 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
345 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
339 end
346 end
340 return
347 return
341 end
348 end
342 render :layout => false if request.xhr?
349 render :layout => false if request.xhr?
343 end
350 end
344
351
345 def destroy
352 def destroy
346 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
353 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
347 if @hours > 0
354 if @hours > 0
348 case params[:todo]
355 case params[:todo]
349 when 'destroy'
356 when 'destroy'
350 # nothing to do
357 # nothing to do
351 when 'nullify'
358 when 'nullify'
352 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
359 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
353 when 'reassign'
360 when 'reassign'
354 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
361 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
355 if reassign_to.nil?
362 if reassign_to.nil?
356 flash.now[:error] = l(:error_issue_not_found_in_project)
363 flash.now[:error] = l(:error_issue_not_found_in_project)
357 return
364 return
358 else
365 else
359 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
366 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
360 end
367 end
361 else
368 else
362 unless params[:format] == 'xml'
369 unless params[:format] == 'xml'
363 # display the destroy form if it's a user request
370 # display the destroy form if it's a user request
364 return
371 return
365 end
372 end
366 end
373 end
367 end
374 end
368 @issues.each(&:destroy)
375 @issues.each(&:destroy)
369 respond_to do |format|
376 respond_to do |format|
370 format.html { redirect_to :action => 'index', :project_id => @project }
377 format.html { redirect_to :action => 'index', :project_id => @project }
371 format.xml { head :ok }
378 format.xml { head :ok }
372 end
379 end
373 end
380 end
374
381
375 def gantt
382 def gantt
376 @gantt = Redmine::Helpers::Gantt.new(params)
383 @gantt = Redmine::Helpers::Gantt.new(params)
377 retrieve_query
384 retrieve_query
378 @query.group_by = nil
385 @query.group_by = nil
379 if @query.valid?
386 if @query.valid?
380 events = []
387 events = []
381 # Issues that have start and due dates
388 # Issues that have start and due dates
382 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
389 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
383 :order => "start_date, due_date",
390 :order => "start_date, due_date",
384 :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]
391 :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]
385 )
392 )
386 # Issues that don't have a due date but that are assigned to a version with a date
393 # Issues that don't have a due date but that are assigned to a version with a date
387 events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
394 events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
388 :order => "start_date, effective_date",
395 :order => "start_date, effective_date",
389 :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]
396 :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]
390 )
397 )
391 # Versions
398 # Versions
392 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
399 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
393
400
394 @gantt.events = events
401 @gantt.events = events
395 end
402 end
396
403
397 basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
404 basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
398
405
399 respond_to do |format|
406 respond_to do |format|
400 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
407 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
401 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
408 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
402 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
409 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
403 end
410 end
404 end
411 end
405
412
406 def calendar
413 def calendar
407 if params[:year] and params[:year].to_i > 1900
414 if params[:year] and params[:year].to_i > 1900
408 @year = params[:year].to_i
415 @year = params[:year].to_i
409 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
416 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
410 @month = params[:month].to_i
417 @month = params[:month].to_i
411 end
418 end
412 end
419 end
413 @year ||= Date.today.year
420 @year ||= Date.today.year
414 @month ||= Date.today.month
421 @month ||= Date.today.month
415
422
416 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
423 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
417 retrieve_query
424 retrieve_query
418 @query.group_by = nil
425 @query.group_by = nil
419 if @query.valid?
426 if @query.valid?
420 events = []
427 events = []
421 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
428 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
422 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
429 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
423 )
430 )
424 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
431 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
425
432
426 @calendar.events = events
433 @calendar.events = events
427 end
434 end
428
435
429 render :layout => false if request.xhr?
436 render :layout => false if request.xhr?
430 end
437 end
431
438
432 def context_menu
439 def context_menu
433 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
440 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
434 if (@issues.size == 1)
441 if (@issues.size == 1)
435 @issue = @issues.first
442 @issue = @issues.first
436 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
443 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
437 end
444 end
438 projects = @issues.collect(&:project).compact.uniq
445 projects = @issues.collect(&:project).compact.uniq
439 @project = projects.first if projects.size == 1
446 @project = projects.first if projects.size == 1
440
447
441 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
448 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
442 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
449 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
443 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
450 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
444 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
451 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
445 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
452 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
446 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
453 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
447 }
454 }
448 if @project
455 if @project
449 @assignables = @project.assignable_users
456 @assignables = @project.assignable_users
450 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
457 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
451 @trackers = @project.trackers
458 @trackers = @project.trackers
452 end
459 end
453
460
454 @priorities = IssuePriority.all.reverse
461 @priorities = IssuePriority.all.reverse
455 @statuses = IssueStatus.find(:all, :order => 'position')
462 @statuses = IssueStatus.find(:all, :order => 'position')
456 @back = params[:back_url] || request.env['HTTP_REFERER']
463 @back = params[:back_url] || request.env['HTTP_REFERER']
457
464
458 render :layout => false
465 render :layout => false
459 end
466 end
460
467
461 def update_form
468 def update_form
462 if params[:id].blank?
469 if params[:id].blank?
463 @issue = Issue.new
470 @issue = Issue.new
464 @issue.project = @project
471 @issue.project = @project
465 else
472 else
466 @issue = @project.issues.visible.find(params[:id])
473 @issue = @project.issues.visible.find(params[:id])
467 end
474 end
468 @issue.attributes = params[:issue]
475 @issue.attributes = params[:issue]
469 @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
476 @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
470 @priorities = IssuePriority.all
477 @priorities = IssuePriority.all
471
478
472 render :partial => 'attributes'
479 render :partial => 'attributes'
473 end
480 end
474
481
475 def preview
482 def preview
476 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
483 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
477 @attachements = @issue.attachments if @issue
484 @attachements = @issue.attachments if @issue
478 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
485 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
479 render :partial => 'common/preview'
486 render :partial => 'common/preview'
480 end
487 end
481
488
482 private
489 private
483 def find_issue
490 def find_issue
484 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
491 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
485 @project = @issue.project
492 @project = @issue.project
486 rescue ActiveRecord::RecordNotFound
493 rescue ActiveRecord::RecordNotFound
487 render_404
494 render_404
488 end
495 end
489
496
490 # Filter for bulk operations
497 # Filter for bulk operations
491 def find_issues
498 def find_issues
492 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
499 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
493 raise ActiveRecord::RecordNotFound if @issues.empty?
500 raise ActiveRecord::RecordNotFound if @issues.empty?
494 projects = @issues.collect(&:project).compact.uniq
501 projects = @issues.collect(&:project).compact.uniq
495 if projects.size == 1
502 if projects.size == 1
496 @project = projects.first
503 @project = projects.first
497 else
504 else
498 # TODO: let users bulk edit/move/destroy issues from different projects
505 # TODO: let users bulk edit/move/destroy issues from different projects
499 render_error 'Can not bulk edit/move/destroy issues from different projects'
506 render_error 'Can not bulk edit/move/destroy issues from different projects'
500 return false
507 return false
501 end
508 end
502 rescue ActiveRecord::RecordNotFound
509 rescue ActiveRecord::RecordNotFound
503 render_404
510 render_404
504 end
511 end
505
512
506 def find_project
513 def find_project
507 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
514 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
508 @project = Project.find(project_id)
515 @project = Project.find(project_id)
509 rescue ActiveRecord::RecordNotFound
516 rescue ActiveRecord::RecordNotFound
510 render_404
517 render_404
511 end
518 end
512
519
513 def find_optional_project
520 def find_optional_project
514 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
521 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
515 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
522 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
516 allowed ? true : deny_access
523 allowed ? true : deny_access
517 rescue ActiveRecord::RecordNotFound
524 rescue ActiveRecord::RecordNotFound
518 render_404
525 render_404
519 end
526 end
520
527
521 # Retrieve query from session or build a new query
528 # Retrieve query from session or build a new query
522 def retrieve_query
529 def retrieve_query
523 if !params[:query_id].blank?
530 if !params[:query_id].blank?
524 cond = "project_id IS NULL"
531 cond = "project_id IS NULL"
525 cond << " OR project_id = #{@project.id}" if @project
532 cond << " OR project_id = #{@project.id}" if @project
526 @query = Query.find(params[:query_id], :conditions => cond)
533 @query = Query.find(params[:query_id], :conditions => cond)
527 @query.project = @project
534 @query.project = @project
528 session[:query] = {:id => @query.id, :project_id => @query.project_id}
535 session[:query] = {:id => @query.id, :project_id => @query.project_id}
529 sort_clear
536 sort_clear
530 else
537 else
531 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
538 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
532 # Give it a name, required to be valid
539 # Give it a name, required to be valid
533 @query = Query.new(:name => "_")
540 @query = Query.new(:name => "_")
534 @query.project = @project
541 @query.project = @project
535 if params[:fields] and params[:fields].is_a? Array
542 if params[:fields] and params[:fields].is_a? Array
536 params[:fields].each do |field|
543 params[:fields].each do |field|
537 @query.add_filter(field, params[:operators][field], params[:values][field])
544 @query.add_filter(field, params[:operators][field], params[:values][field])
538 end
545 end
539 else
546 else
540 @query.available_filters.keys.each do |field|
547 @query.available_filters.keys.each do |field|
541 @query.add_short_filter(field, params[field]) if params[field]
548 @query.add_short_filter(field, params[field]) if params[field]
542 end
549 end
543 end
550 end
544 @query.group_by = params[:group_by]
551 @query.group_by = params[:group_by]
545 @query.column_names = params[:query] && params[:query][:column_names]
552 @query.column_names = params[:query] && params[:query][:column_names]
546 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
553 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
547 else
554 else
548 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
555 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
549 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
556 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
550 @query.project = @project
557 @query.project = @project
551 end
558 end
552 end
559 end
553 end
560 end
554
561
555 # Rescues an invalid query statement. Just in case...
562 # Rescues an invalid query statement. Just in case...
556 def query_statement_invalid(exception)
563 def query_statement_invalid(exception)
557 logger.error "Query::StatementInvalid: #{exception.message}" if logger
564 logger.error "Query::StatementInvalid: #{exception.message}" if logger
558 session.delete(:query)
565 session.delete(:query)
559 sort_clear
566 sort_clear
560 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
567 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
561 end
568 end
562 end
569 end
@@ -1,55 +1,56
1 <% labelled_tabular_form_for :issue, @issue,
1 <% labelled_tabular_form_for :issue, @issue,
2 :url => {:action => 'edit', :id => @issue},
2 :url => {:action => 'update', :id => @issue},
3 :html => {:id => 'issue-form',
3 :html => {:id => 'issue-form',
4 :class => nil,
4 :class => nil,
5 :method => :put,
5 :multipart => true} do |f| %>
6 :multipart => true} do |f| %>
6 <%= error_messages_for 'issue' %>
7 <%= error_messages_for 'issue' %>
7 <%= error_messages_for 'time_entry' %>
8 <%= error_messages_for 'time_entry' %>
8 <div class="box">
9 <div class="box">
9 <% if @edit_allowed || !@allowed_statuses.empty? %>
10 <% if @edit_allowed || !@allowed_statuses.empty? %>
10 <fieldset class="tabular"><legend><%= l(:label_change_properties) %>
11 <fieldset class="tabular"><legend><%= l(:label_change_properties) %>
11 <% if !@issue.new_record? && !@issue.errors.any? && @edit_allowed %>
12 <% if !@issue.new_record? && !@issue.errors.any? && @edit_allowed %>
12 <small>(<%= link_to l(:label_more), {}, :onclick => 'Effect.toggle("issue_descr_fields", "appear", {duration:0.3}); return false;' %>)</small>
13 <small>(<%= link_to l(:label_more), {}, :onclick => 'Effect.toggle("issue_descr_fields", "appear", {duration:0.3}); return false;' %>)</small>
13 <% end %>
14 <% end %>
14 </legend>
15 </legend>
15 <%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
16 <%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
16 </fieldset>
17 </fieldset>
17 <% end %>
18 <% end %>
18 <% if authorize_for('timelog', 'edit') %>
19 <% if authorize_for('timelog', 'edit') %>
19 <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
20 <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
20 <% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
21 <% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
21 <div class="splitcontentleft">
22 <div class="splitcontentleft">
22 <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
23 <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
23 </div>
24 </div>
24 <div class="splitcontentright">
25 <div class="splitcontentright">
25 <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
26 <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
26 </div>
27 </div>
27 <p><%= time_entry.text_field :comments, :size => 60 %></p>
28 <p><%= time_entry.text_field :comments, :size => 60 %></p>
28 <% @time_entry.custom_field_values.each do |value| %>
29 <% @time_entry.custom_field_values.each do |value| %>
29 <p><%= custom_field_tag_with_label :time_entry, value %></p>
30 <p><%= custom_field_tag_with_label :time_entry, value %></p>
30 <% end %>
31 <% end %>
31 <% end %>
32 <% end %>
32 </fieldset>
33 </fieldset>
33 <% end %>
34 <% end %>
34
35
35 <fieldset><legend><%= l(:field_notes) %></legend>
36 <fieldset><legend><%= l(:field_notes) %></legend>
36 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
37 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
37 <%= wikitoolbar_for 'notes' %>
38 <%= wikitoolbar_for 'notes' %>
38 <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
39 <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
39
40
40 <p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form' %></p>
41 <p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form' %></p>
41 </fieldset>
42 </fieldset>
42 </div>
43 </div>
43
44
44 <%= f.hidden_field :lock_version %>
45 <%= f.hidden_field :lock_version %>
45 <%= submit_tag l(:button_submit) %>
46 <%= submit_tag l(:button_submit) %>
46 <%= link_to_remote l(:label_preview),
47 <%= link_to_remote l(:label_preview),
47 { :url => { :controller => 'issues', :action => 'preview', :project_id => @project, :id => @issue },
48 { :url => { :controller => 'issues', :action => 'preview', :project_id => @project, :id => @issue },
48 :method => 'post',
49 :method => 'post',
49 :update => 'preview',
50 :update => 'preview',
50 :with => 'Form.serialize("issue-form")',
51 :with => 'Form.serialize("issue-form")',
51 :complete => "Element.scrollTo('preview')"
52 :complete => "Element.scrollTo('preview')"
52 }, :accesskey => accesskey(:preview) %>
53 }, :accesskey => accesskey(:preview) %>
53 <% end %>
54 <% end %>
54
55
55 <div id="preview" class="wiki"></div>
56 <div id="preview" class="wiki"></div>
@@ -1,194 +1,194
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/activity'
3 require 'redmine/activity'
4 require 'redmine/search'
4 require 'redmine/search'
5 require 'redmine/mime_type'
5 require 'redmine/mime_type'
6 require 'redmine/core_ext'
6 require 'redmine/core_ext'
7 require 'redmine/themes'
7 require 'redmine/themes'
8 require 'redmine/hook'
8 require 'redmine/hook'
9 require 'redmine/plugin'
9 require 'redmine/plugin'
10 require 'redmine/wiki_formatting'
10 require 'redmine/wiki_formatting'
11 require 'redmine/scm/base'
11 require 'redmine/scm/base'
12
12
13 begin
13 begin
14 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
14 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
15 rescue LoadError
15 rescue LoadError
16 # RMagick is not available
16 # RMagick is not available
17 end
17 end
18
18
19 if RUBY_VERSION < '1.9'
19 if RUBY_VERSION < '1.9'
20 require 'faster_csv'
20 require 'faster_csv'
21 else
21 else
22 require 'csv'
22 require 'csv'
23 FCSV = CSV
23 FCSV = CSV
24 end
24 end
25
25
26 Redmine::Scm::Base.add "Subversion"
26 Redmine::Scm::Base.add "Subversion"
27 Redmine::Scm::Base.add "Darcs"
27 Redmine::Scm::Base.add "Darcs"
28 Redmine::Scm::Base.add "Mercurial"
28 Redmine::Scm::Base.add "Mercurial"
29 Redmine::Scm::Base.add "Cvs"
29 Redmine::Scm::Base.add "Cvs"
30 Redmine::Scm::Base.add "Bazaar"
30 Redmine::Scm::Base.add "Bazaar"
31 Redmine::Scm::Base.add "Git"
31 Redmine::Scm::Base.add "Git"
32 Redmine::Scm::Base.add "Filesystem"
32 Redmine::Scm::Base.add "Filesystem"
33
33
34 # Permissions
34 # Permissions
35 Redmine::AccessControl.map do |map|
35 Redmine::AccessControl.map do |map|
36 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
36 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
37 map.permission :search_project, {:search => :index}, :public => true
37 map.permission :search_project, {:search => :index}, :public => true
38 map.permission :add_project, {:projects => :add}, :require => :loggedin
38 map.permission :add_project, {:projects => :add}, :require => :loggedin
39 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
39 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
40 map.permission :select_project_modules, {:projects => :modules}, :require => :member
40 map.permission :select_project_modules, {:projects => :modules}, :require => :member
41 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
41 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
42 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :close_completed, :destroy]}, :require => :member
42 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :close_completed, :destroy]}, :require => :member
43 map.permission :add_subprojects, {:projects => :add}, :require => :member
43 map.permission :add_subprojects, {:projects => :add}, :require => :member
44
44
45 map.project_module :issue_tracking do |map|
45 map.project_module :issue_tracking do |map|
46 # Issue categories
46 # Issue categories
47 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
47 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
48 # Issues
48 # Issues
49 map.permission :view_issues, {:projects => :roadmap,
49 map.permission :view_issues, {:projects => :roadmap,
50 :issues => [:index, :changes, :show, :context_menu],
50 :issues => [:index, :changes, :show, :context_menu],
51 :versions => [:show, :status_by],
51 :versions => [:show, :status_by],
52 :queries => :index,
52 :queries => :index,
53 :reports => [:issue_report, :issue_report_details]}
53 :reports => [:issue_report, :issue_report_details]}
54 map.permission :add_issues, {:issues => [:new, :update_form]}
54 map.permission :add_issues, {:issues => [:new, :update_form]}
55 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :update_form]}
55 map.permission :edit_issues, {:issues => [:edit, :update, :reply, :bulk_edit, :update_form]}
56 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
56 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
57 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
57 map.permission :add_issue_notes, {:issues => [:edit, :update, :reply]}
58 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
58 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
59 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
59 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
60 map.permission :move_issues, {:issues => :move}, :require => :loggedin
60 map.permission :move_issues, {:issues => :move}, :require => :loggedin
61 map.permission :delete_issues, {:issues => :destroy}, :require => :member
61 map.permission :delete_issues, {:issues => :destroy}, :require => :member
62 # Queries
62 # Queries
63 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
63 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
64 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
64 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
65 # Gantt & calendar
65 # Gantt & calendar
66 map.permission :view_gantt, :issues => :gantt
66 map.permission :view_gantt, :issues => :gantt
67 map.permission :view_calendar, :issues => :calendar
67 map.permission :view_calendar, :issues => :calendar
68 # Watchers
68 # Watchers
69 map.permission :view_issue_watchers, {}
69 map.permission :view_issue_watchers, {}
70 map.permission :add_issue_watchers, {:watchers => :new}
70 map.permission :add_issue_watchers, {:watchers => :new}
71 map.permission :delete_issue_watchers, {:watchers => :destroy}
71 map.permission :delete_issue_watchers, {:watchers => :destroy}
72 end
72 end
73
73
74 map.project_module :time_tracking do |map|
74 map.project_module :time_tracking do |map|
75 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
75 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
76 map.permission :view_time_entries, :timelog => [:details, :report]
76 map.permission :view_time_entries, :timelog => [:details, :report]
77 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
77 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
78 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
78 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
79 map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
79 map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
80 end
80 end
81
81
82 map.project_module :news do |map|
82 map.project_module :news do |map|
83 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
83 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
84 map.permission :view_news, {:news => [:index, :show]}, :public => true
84 map.permission :view_news, {:news => [:index, :show]}, :public => true
85 map.permission :comment_news, {:news => :add_comment}
85 map.permission :comment_news, {:news => :add_comment}
86 end
86 end
87
87
88 map.project_module :documents do |map|
88 map.project_module :documents do |map|
89 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
89 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
90 map.permission :view_documents, :documents => [:index, :show, :download]
90 map.permission :view_documents, :documents => [:index, :show, :download]
91 end
91 end
92
92
93 map.project_module :files do |map|
93 map.project_module :files do |map|
94 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
94 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
95 map.permission :view_files, :projects => :list_files, :versions => :download
95 map.permission :view_files, :projects => :list_files, :versions => :download
96 end
96 end
97
97
98 map.project_module :wiki do |map|
98 map.project_module :wiki do |map|
99 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
99 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
100 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
100 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
101 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
101 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
102 map.permission :view_wiki_pages, :wiki => [:index, :special]
102 map.permission :view_wiki_pages, :wiki => [:index, :special]
103 map.permission :export_wiki_pages, {}
103 map.permission :export_wiki_pages, {}
104 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
104 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
105 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
105 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
106 map.permission :delete_wiki_pages_attachments, {}
106 map.permission :delete_wiki_pages_attachments, {}
107 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
107 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
108 end
108 end
109
109
110 map.project_module :repository do |map|
110 map.project_module :repository do |map|
111 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
111 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
112 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
112 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
113 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
113 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
114 map.permission :commit_access, {}
114 map.permission :commit_access, {}
115 end
115 end
116
116
117 map.project_module :boards do |map|
117 map.project_module :boards do |map|
118 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
118 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
119 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
119 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
120 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
120 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
121 map.permission :edit_messages, {:messages => :edit}, :require => :member
121 map.permission :edit_messages, {:messages => :edit}, :require => :member
122 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
122 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
123 map.permission :delete_messages, {:messages => :destroy}, :require => :member
123 map.permission :delete_messages, {:messages => :destroy}, :require => :member
124 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
124 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
125 end
125 end
126 end
126 end
127
127
128 Redmine::MenuManager.map :top_menu do |menu|
128 Redmine::MenuManager.map :top_menu do |menu|
129 menu.push :home, :home_path
129 menu.push :home, :home_path
130 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
130 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
131 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
131 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
132 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
132 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
133 menu.push :help, Redmine::Info.help_url, :last => true
133 menu.push :help, Redmine::Info.help_url, :last => true
134 end
134 end
135
135
136 Redmine::MenuManager.map :account_menu do |menu|
136 Redmine::MenuManager.map :account_menu do |menu|
137 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
137 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
138 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
138 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
139 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
139 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
140 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
140 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
141 end
141 end
142
142
143 Redmine::MenuManager.map :application_menu do |menu|
143 Redmine::MenuManager.map :application_menu do |menu|
144 # Empty
144 # Empty
145 end
145 end
146
146
147 Redmine::MenuManager.map :admin_menu do |menu|
147 Redmine::MenuManager.map :admin_menu do |menu|
148 # Empty
148 # Empty
149 end
149 end
150
150
151 Redmine::MenuManager.map :project_menu do |menu|
151 Redmine::MenuManager.map :project_menu do |menu|
152 menu.push :overview, { :controller => 'projects', :action => 'show' }
152 menu.push :overview, { :controller => 'projects', :action => 'show' }
153 menu.push :activity, { :controller => 'projects', :action => 'activity' }
153 menu.push :activity, { :controller => 'projects', :action => 'activity' }
154 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
154 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
155 :if => Proc.new { |p| p.shared_versions.any? }
155 :if => Proc.new { |p| p.shared_versions.any? }
156 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
156 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
157 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
157 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
158 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
158 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
159 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
159 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
160 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
160 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
161 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
161 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
162 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
162 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
163 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
163 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
164 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
164 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
165 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_file_plural
165 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_file_plural
166 menu.push :repository, { :controller => 'repositories', :action => 'show' },
166 menu.push :repository, { :controller => 'repositories', :action => 'show' },
167 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
167 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
168 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
168 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
169 end
169 end
170
170
171 Redmine::Activity.map do |activity|
171 Redmine::Activity.map do |activity|
172 activity.register :issues, :class_name => %w(Issue Journal)
172 activity.register :issues, :class_name => %w(Issue Journal)
173 activity.register :changesets
173 activity.register :changesets
174 activity.register :news
174 activity.register :news
175 activity.register :documents, :class_name => %w(Document Attachment)
175 activity.register :documents, :class_name => %w(Document Attachment)
176 activity.register :files, :class_name => 'Attachment'
176 activity.register :files, :class_name => 'Attachment'
177 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
177 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
178 activity.register :messages, :default => false
178 activity.register :messages, :default => false
179 activity.register :time_entries, :default => false
179 activity.register :time_entries, :default => false
180 end
180 end
181
181
182 Redmine::Search.map do |search|
182 Redmine::Search.map do |search|
183 search.register :issues
183 search.register :issues
184 search.register :news
184 search.register :news
185 search.register :documents
185 search.register :documents
186 search.register :changesets
186 search.register :changesets
187 search.register :wiki_pages
187 search.register :wiki_pages
188 search.register :messages
188 search.register :messages
189 search.register :projects
189 search.register :projects
190 end
190 end
191
191
192 Redmine::WikiFormatting.map do |format|
192 Redmine::WikiFormatting.map do |format|
193 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
193 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
194 end
194 end
@@ -1,1233 +1,1233
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
56 def test_index
57 Setting.default_language = 'en'
57 Setting.default_language = 'en'
58
58
59 get :index
59 get :index
60 assert_response :success
60 assert_response :success
61 assert_template 'index.rhtml'
61 assert_template 'index.rhtml'
62 assert_not_nil assigns(:issues)
62 assert_not_nil assigns(:issues)
63 assert_nil assigns(:project)
63 assert_nil assigns(:project)
64 assert_tag :tag => 'a', :content => /Can't print recipes/
64 assert_tag :tag => 'a', :content => /Can't print recipes/
65 assert_tag :tag => 'a', :content => /Subproject issue/
65 assert_tag :tag => 'a', :content => /Subproject issue/
66 # private projects hidden
66 # private projects hidden
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
69 # project column
69 # project column
70 assert_tag :tag => 'th', :content => /Project/
70 assert_tag :tag => 'th', :content => /Project/
71 end
71 end
72
72
73 def test_index_should_not_list_issues_when_module_disabled
73 def test_index_should_not_list_issues_when_module_disabled
74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 get :index
75 get :index
76 assert_response :success
76 assert_response :success
77 assert_template 'index.rhtml'
77 assert_template 'index.rhtml'
78 assert_not_nil assigns(:issues)
78 assert_not_nil assigns(:issues)
79 assert_nil assigns(:project)
79 assert_nil assigns(:project)
80 assert_no_tag :tag => 'a', :content => /Can't print recipes/
80 assert_no_tag :tag => 'a', :content => /Can't print recipes/
81 assert_tag :tag => 'a', :content => /Subproject issue/
81 assert_tag :tag => 'a', :content => /Subproject issue/
82 end
82 end
83
83
84 def test_index_should_not_list_issues_when_module_disabled
84 def test_index_should_not_list_issues_when_module_disabled
85 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
85 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
86 get :index
86 get :index
87 assert_response :success
87 assert_response :success
88 assert_template 'index.rhtml'
88 assert_template 'index.rhtml'
89 assert_not_nil assigns(:issues)
89 assert_not_nil assigns(:issues)
90 assert_nil assigns(:project)
90 assert_nil assigns(:project)
91 assert_no_tag :tag => 'a', :content => /Can't print recipes/
91 assert_no_tag :tag => 'a', :content => /Can't print recipes/
92 assert_tag :tag => 'a', :content => /Subproject issue/
92 assert_tag :tag => 'a', :content => /Subproject issue/
93 end
93 end
94
94
95 def test_index_with_project
95 def test_index_with_project
96 Setting.display_subprojects_issues = 0
96 Setting.display_subprojects_issues = 0
97 get :index, :project_id => 1
97 get :index, :project_id => 1
98 assert_response :success
98 assert_response :success
99 assert_template 'index.rhtml'
99 assert_template 'index.rhtml'
100 assert_not_nil assigns(:issues)
100 assert_not_nil assigns(:issues)
101 assert_tag :tag => 'a', :content => /Can't print recipes/
101 assert_tag :tag => 'a', :content => /Can't print recipes/
102 assert_no_tag :tag => 'a', :content => /Subproject issue/
102 assert_no_tag :tag => 'a', :content => /Subproject issue/
103 end
103 end
104
104
105 def test_index_with_project_and_subprojects
105 def test_index_with_project_and_subprojects
106 Setting.display_subprojects_issues = 1
106 Setting.display_subprojects_issues = 1
107 get :index, :project_id => 1
107 get :index, :project_id => 1
108 assert_response :success
108 assert_response :success
109 assert_template 'index.rhtml'
109 assert_template 'index.rhtml'
110 assert_not_nil assigns(:issues)
110 assert_not_nil assigns(:issues)
111 assert_tag :tag => 'a', :content => /Can't print recipes/
111 assert_tag :tag => 'a', :content => /Can't print recipes/
112 assert_tag :tag => 'a', :content => /Subproject issue/
112 assert_tag :tag => 'a', :content => /Subproject issue/
113 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
113 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
114 end
114 end
115
115
116 def test_index_with_project_and_subprojects_should_show_private_subprojects
116 def test_index_with_project_and_subprojects_should_show_private_subprojects
117 @request.session[:user_id] = 2
117 @request.session[:user_id] = 2
118 Setting.display_subprojects_issues = 1
118 Setting.display_subprojects_issues = 1
119 get :index, :project_id => 1
119 get :index, :project_id => 1
120 assert_response :success
120 assert_response :success
121 assert_template 'index.rhtml'
121 assert_template 'index.rhtml'
122 assert_not_nil assigns(:issues)
122 assert_not_nil assigns(:issues)
123 assert_tag :tag => 'a', :content => /Can't print recipes/
123 assert_tag :tag => 'a', :content => /Can't print recipes/
124 assert_tag :tag => 'a', :content => /Subproject issue/
124 assert_tag :tag => 'a', :content => /Subproject issue/
125 assert_tag :tag => 'a', :content => /Issue of a private subproject/
125 assert_tag :tag => 'a', :content => /Issue of a private subproject/
126 end
126 end
127
127
128 def test_index_with_project_and_filter
128 def test_index_with_project_and_filter
129 get :index, :project_id => 1, :set_filter => 1
129 get :index, :project_id => 1, :set_filter => 1
130 assert_response :success
130 assert_response :success
131 assert_template 'index.rhtml'
131 assert_template 'index.rhtml'
132 assert_not_nil assigns(:issues)
132 assert_not_nil assigns(:issues)
133 end
133 end
134
134
135 def test_index_with_query
135 def test_index_with_query
136 get :index, :project_id => 1, :query_id => 5
136 get :index, :project_id => 1, :query_id => 5
137 assert_response :success
137 assert_response :success
138 assert_template 'index.rhtml'
138 assert_template 'index.rhtml'
139 assert_not_nil assigns(:issues)
139 assert_not_nil assigns(:issues)
140 assert_nil assigns(:issue_count_by_group)
140 assert_nil assigns(:issue_count_by_group)
141 end
141 end
142
142
143 def test_index_with_query_grouped_by_tracker
143 def test_index_with_query_grouped_by_tracker
144 get :index, :project_id => 1, :query_id => 6
144 get :index, :project_id => 1, :query_id => 6
145 assert_response :success
145 assert_response :success
146 assert_template 'index.rhtml'
146 assert_template 'index.rhtml'
147 assert_not_nil assigns(:issues)
147 assert_not_nil assigns(:issues)
148 assert_not_nil assigns(:issue_count_by_group)
148 assert_not_nil assigns(:issue_count_by_group)
149 end
149 end
150
150
151 def test_index_with_query_grouped_by_list_custom_field
151 def test_index_with_query_grouped_by_list_custom_field
152 get :index, :project_id => 1, :query_id => 9
152 get :index, :project_id => 1, :query_id => 9
153 assert_response :success
153 assert_response :success
154 assert_template 'index.rhtml'
154 assert_template 'index.rhtml'
155 assert_not_nil assigns(:issues)
155 assert_not_nil assigns(:issues)
156 assert_not_nil assigns(:issue_count_by_group)
156 assert_not_nil assigns(:issue_count_by_group)
157 end
157 end
158
158
159 def test_index_sort_by_field_not_included_in_columns
159 def test_index_sort_by_field_not_included_in_columns
160 Setting.issue_list_default_columns = %w(subject author)
160 Setting.issue_list_default_columns = %w(subject author)
161 get :index, :sort => 'tracker'
161 get :index, :sort => 'tracker'
162 end
162 end
163
163
164 def test_index_csv_with_project
164 def test_index_csv_with_project
165 Setting.default_language = 'en'
165 Setting.default_language = 'en'
166
166
167 get :index, :format => 'csv'
167 get :index, :format => 'csv'
168 assert_response :success
168 assert_response :success
169 assert_not_nil assigns(:issues)
169 assert_not_nil assigns(:issues)
170 assert_equal 'text/csv', @response.content_type
170 assert_equal 'text/csv', @response.content_type
171 assert @response.body.starts_with?("#,")
171 assert @response.body.starts_with?("#,")
172
172
173 get :index, :project_id => 1, :format => 'csv'
173 get :index, :project_id => 1, :format => 'csv'
174 assert_response :success
174 assert_response :success
175 assert_not_nil assigns(:issues)
175 assert_not_nil assigns(:issues)
176 assert_equal 'text/csv', @response.content_type
176 assert_equal 'text/csv', @response.content_type
177 end
177 end
178
178
179 def test_index_pdf
179 def test_index_pdf
180 get :index, :format => 'pdf'
180 get :index, :format => 'pdf'
181 assert_response :success
181 assert_response :success
182 assert_not_nil assigns(:issues)
182 assert_not_nil assigns(:issues)
183 assert_equal 'application/pdf', @response.content_type
183 assert_equal 'application/pdf', @response.content_type
184
184
185 get :index, :project_id => 1, :format => 'pdf'
185 get :index, :project_id => 1, :format => 'pdf'
186 assert_response :success
186 assert_response :success
187 assert_not_nil assigns(:issues)
187 assert_not_nil assigns(:issues)
188 assert_equal 'application/pdf', @response.content_type
188 assert_equal 'application/pdf', @response.content_type
189
189
190 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
190 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
191 assert_response :success
191 assert_response :success
192 assert_not_nil assigns(:issues)
192 assert_not_nil assigns(:issues)
193 assert_equal 'application/pdf', @response.content_type
193 assert_equal 'application/pdf', @response.content_type
194 end
194 end
195
195
196 def test_index_pdf_with_query_grouped_by_list_custom_field
196 def test_index_pdf_with_query_grouped_by_list_custom_field
197 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
197 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
198 assert_response :success
198 assert_response :success
199 assert_not_nil assigns(:issues)
199 assert_not_nil assigns(:issues)
200 assert_not_nil assigns(:issue_count_by_group)
200 assert_not_nil assigns(:issue_count_by_group)
201 assert_equal 'application/pdf', @response.content_type
201 assert_equal 'application/pdf', @response.content_type
202 end
202 end
203
203
204 def test_index_sort
204 def test_index_sort
205 get :index, :sort => 'tracker,id:desc'
205 get :index, :sort => 'tracker,id:desc'
206 assert_response :success
206 assert_response :success
207
207
208 sort_params = @request.session['issues_index_sort']
208 sort_params = @request.session['issues_index_sort']
209 assert sort_params.is_a?(String)
209 assert sort_params.is_a?(String)
210 assert_equal 'tracker,id:desc', sort_params
210 assert_equal 'tracker,id:desc', sort_params
211
211
212 issues = assigns(:issues)
212 issues = assigns(:issues)
213 assert_not_nil issues
213 assert_not_nil issues
214 assert !issues.empty?
214 assert !issues.empty?
215 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
215 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
216 end
216 end
217
217
218 def test_index_with_columns
218 def test_index_with_columns
219 columns = ['tracker', 'subject', 'assigned_to']
219 columns = ['tracker', 'subject', 'assigned_to']
220 get :index, :set_filter => 1, :query => { 'column_names' => columns}
220 get :index, :set_filter => 1, :query => { 'column_names' => columns}
221 assert_response :success
221 assert_response :success
222
222
223 # query should use specified columns
223 # query should use specified columns
224 query = assigns(:query)
224 query = assigns(:query)
225 assert_kind_of Query, query
225 assert_kind_of Query, query
226 assert_equal columns, query.column_names.map(&:to_s)
226 assert_equal columns, query.column_names.map(&:to_s)
227
227
228 # columns should be stored in session
228 # columns should be stored in session
229 assert_kind_of Hash, session[:query]
229 assert_kind_of Hash, session[:query]
230 assert_kind_of Array, session[:query][:column_names]
230 assert_kind_of Array, session[:query][:column_names]
231 assert_equal columns, session[:query][:column_names].map(&:to_s)
231 assert_equal columns, session[:query][:column_names].map(&:to_s)
232 end
232 end
233
233
234 def test_gantt
234 def test_gantt
235 get :gantt, :project_id => 1
235 get :gantt, :project_id => 1
236 assert_response :success
236 assert_response :success
237 assert_template 'gantt.rhtml'
237 assert_template 'gantt.rhtml'
238 assert_not_nil assigns(:gantt)
238 assert_not_nil assigns(:gantt)
239 events = assigns(:gantt).events
239 events = assigns(:gantt).events
240 assert_not_nil events
240 assert_not_nil events
241 # Issue with start and due dates
241 # Issue with start and due dates
242 i = Issue.find(1)
242 i = Issue.find(1)
243 assert_not_nil i.due_date
243 assert_not_nil i.due_date
244 assert events.include?(Issue.find(1))
244 assert events.include?(Issue.find(1))
245 # Issue with without due date but targeted to a version with date
245 # Issue with without due date but targeted to a version with date
246 i = Issue.find(2)
246 i = Issue.find(2)
247 assert_nil i.due_date
247 assert_nil i.due_date
248 assert events.include?(i)
248 assert events.include?(i)
249 end
249 end
250
250
251 def test_cross_project_gantt
251 def test_cross_project_gantt
252 get :gantt
252 get :gantt
253 assert_response :success
253 assert_response :success
254 assert_template 'gantt.rhtml'
254 assert_template 'gantt.rhtml'
255 assert_not_nil assigns(:gantt)
255 assert_not_nil assigns(:gantt)
256 events = assigns(:gantt).events
256 events = assigns(:gantt).events
257 assert_not_nil events
257 assert_not_nil events
258 end
258 end
259
259
260 def test_gantt_export_to_pdf
260 def test_gantt_export_to_pdf
261 get :gantt, :project_id => 1, :format => 'pdf'
261 get :gantt, :project_id => 1, :format => 'pdf'
262 assert_response :success
262 assert_response :success
263 assert_equal 'application/pdf', @response.content_type
263 assert_equal 'application/pdf', @response.content_type
264 assert @response.body.starts_with?('%PDF')
264 assert @response.body.starts_with?('%PDF')
265 assert_not_nil assigns(:gantt)
265 assert_not_nil assigns(:gantt)
266 end
266 end
267
267
268 def test_cross_project_gantt_export_to_pdf
268 def test_cross_project_gantt_export_to_pdf
269 get :gantt, :format => 'pdf'
269 get :gantt, :format => 'pdf'
270 assert_response :success
270 assert_response :success
271 assert_equal 'application/pdf', @response.content_type
271 assert_equal 'application/pdf', @response.content_type
272 assert @response.body.starts_with?('%PDF')
272 assert @response.body.starts_with?('%PDF')
273 assert_not_nil assigns(:gantt)
273 assert_not_nil assigns(:gantt)
274 end
274 end
275
275
276 if Object.const_defined?(:Magick)
276 if Object.const_defined?(:Magick)
277 def test_gantt_image
277 def test_gantt_image
278 get :gantt, :project_id => 1, :format => 'png'
278 get :gantt, :project_id => 1, :format => 'png'
279 assert_response :success
279 assert_response :success
280 assert_equal 'image/png', @response.content_type
280 assert_equal 'image/png', @response.content_type
281 end
281 end
282 else
282 else
283 puts "RMagick not installed. Skipping tests !!!"
283 puts "RMagick not installed. Skipping tests !!!"
284 end
284 end
285
285
286 def test_calendar
286 def test_calendar
287 get :calendar, :project_id => 1
287 get :calendar, :project_id => 1
288 assert_response :success
288 assert_response :success
289 assert_template 'calendar'
289 assert_template 'calendar'
290 assert_not_nil assigns(:calendar)
290 assert_not_nil assigns(:calendar)
291 end
291 end
292
292
293 def test_cross_project_calendar
293 def test_cross_project_calendar
294 get :calendar
294 get :calendar
295 assert_response :success
295 assert_response :success
296 assert_template 'calendar'
296 assert_template 'calendar'
297 assert_not_nil assigns(:calendar)
297 assert_not_nil assigns(:calendar)
298 end
298 end
299
299
300 def test_changes
300 def test_changes
301 get :changes, :project_id => 1
301 get :changes, :project_id => 1
302 assert_response :success
302 assert_response :success
303 assert_not_nil assigns(:journals)
303 assert_not_nil assigns(:journals)
304 assert_equal 'application/atom+xml', @response.content_type
304 assert_equal 'application/atom+xml', @response.content_type
305 end
305 end
306
306
307 def test_show_by_anonymous
307 def test_show_by_anonymous
308 get :show, :id => 1
308 get :show, :id => 1
309 assert_response :success
309 assert_response :success
310 assert_template 'show.rhtml'
310 assert_template 'show.rhtml'
311 assert_not_nil assigns(:issue)
311 assert_not_nil assigns(:issue)
312 assert_equal Issue.find(1), assigns(:issue)
312 assert_equal Issue.find(1), assigns(:issue)
313
313
314 # anonymous role is allowed to add a note
314 # anonymous role is allowed to add a note
315 assert_tag :tag => 'form',
315 assert_tag :tag => 'form',
316 :descendant => { :tag => 'fieldset',
316 :descendant => { :tag => 'fieldset',
317 :child => { :tag => 'legend',
317 :child => { :tag => 'legend',
318 :content => /Notes/ } }
318 :content => /Notes/ } }
319 end
319 end
320
320
321 def test_show_by_manager
321 def test_show_by_manager
322 @request.session[:user_id] = 2
322 @request.session[:user_id] = 2
323 get :show, :id => 1
323 get :show, :id => 1
324 assert_response :success
324 assert_response :success
325
325
326 assert_tag :tag => 'form',
326 assert_tag :tag => 'form',
327 :descendant => { :tag => 'fieldset',
327 :descendant => { :tag => 'fieldset',
328 :child => { :tag => 'legend',
328 :child => { :tag => 'legend',
329 :content => /Change properties/ } },
329 :content => /Change properties/ } },
330 :descendant => { :tag => 'fieldset',
330 :descendant => { :tag => 'fieldset',
331 :child => { :tag => 'legend',
331 :child => { :tag => 'legend',
332 :content => /Log time/ } },
332 :content => /Log time/ } },
333 :descendant => { :tag => 'fieldset',
333 :descendant => { :tag => 'fieldset',
334 :child => { :tag => 'legend',
334 :child => { :tag => 'legend',
335 :content => /Notes/ } }
335 :content => /Notes/ } }
336 end
336 end
337
337
338 def test_show_should_deny_anonymous_access_without_permission
338 def test_show_should_deny_anonymous_access_without_permission
339 Role.anonymous.remove_permission!(:view_issues)
339 Role.anonymous.remove_permission!(:view_issues)
340 get :show, :id => 1
340 get :show, :id => 1
341 assert_response :redirect
341 assert_response :redirect
342 end
342 end
343
343
344 def test_show_should_deny_non_member_access_without_permission
344 def test_show_should_deny_non_member_access_without_permission
345 Role.non_member.remove_permission!(:view_issues)
345 Role.non_member.remove_permission!(:view_issues)
346 @request.session[:user_id] = 9
346 @request.session[:user_id] = 9
347 get :show, :id => 1
347 get :show, :id => 1
348 assert_response 403
348 assert_response 403
349 end
349 end
350
350
351 def test_show_should_deny_member_access_without_permission
351 def test_show_should_deny_member_access_without_permission
352 Role.find(1).remove_permission!(:view_issues)
352 Role.find(1).remove_permission!(:view_issues)
353 @request.session[:user_id] = 2
353 @request.session[:user_id] = 2
354 get :show, :id => 1
354 get :show, :id => 1
355 assert_response 403
355 assert_response 403
356 end
356 end
357
357
358 def test_show_should_not_disclose_relations_to_invisible_issues
358 def test_show_should_not_disclose_relations_to_invisible_issues
359 Setting.cross_project_issue_relations = '1'
359 Setting.cross_project_issue_relations = '1'
360 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
360 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
361 # Relation to a private project issue
361 # Relation to a private project issue
362 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
362 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
363
363
364 get :show, :id => 1
364 get :show, :id => 1
365 assert_response :success
365 assert_response :success
366
366
367 assert_tag :div, :attributes => { :id => 'relations' },
367 assert_tag :div, :attributes => { :id => 'relations' },
368 :descendant => { :tag => 'a', :content => /#2$/ }
368 :descendant => { :tag => 'a', :content => /#2$/ }
369 assert_no_tag :div, :attributes => { :id => 'relations' },
369 assert_no_tag :div, :attributes => { :id => 'relations' },
370 :descendant => { :tag => 'a', :content => /#4$/ }
370 :descendant => { :tag => 'a', :content => /#4$/ }
371 end
371 end
372
372
373 def test_show_atom
373 def test_show_atom
374 get :show, :id => 2, :format => 'atom'
374 get :show, :id => 2, :format => 'atom'
375 assert_response :success
375 assert_response :success
376 assert_template 'changes.rxml'
376 assert_template 'changes.rxml'
377 # Inline image
377 # Inline image
378 assert @response.body.include?("&lt;img src=\"http://test.host/attachments/download/10\" alt=\"\" /&gt;"), "Body did not match. Body: #{@response.body}"
378 assert @response.body.include?("&lt;img src=\"http://test.host/attachments/download/10\" alt=\"\" /&gt;"), "Body did not match. Body: #{@response.body}"
379 end
379 end
380
380
381 def test_show_export_to_pdf
381 def test_show_export_to_pdf
382 get :show, :id => 3, :format => 'pdf'
382 get :show, :id => 3, :format => 'pdf'
383 assert_response :success
383 assert_response :success
384 assert_equal 'application/pdf', @response.content_type
384 assert_equal 'application/pdf', @response.content_type
385 assert @response.body.starts_with?('%PDF')
385 assert @response.body.starts_with?('%PDF')
386 assert_not_nil assigns(:issue)
386 assert_not_nil assigns(:issue)
387 end
387 end
388
388
389 def test_get_new
389 def test_get_new
390 @request.session[:user_id] = 2
390 @request.session[:user_id] = 2
391 get :new, :project_id => 1, :tracker_id => 1
391 get :new, :project_id => 1, :tracker_id => 1
392 assert_response :success
392 assert_response :success
393 assert_template 'new'
393 assert_template 'new'
394
394
395 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
395 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
396 :value => 'Default string' }
396 :value => 'Default string' }
397 end
397 end
398
398
399 def test_get_new_without_tracker_id
399 def test_get_new_without_tracker_id
400 @request.session[:user_id] = 2
400 @request.session[:user_id] = 2
401 get :new, :project_id => 1
401 get :new, :project_id => 1
402 assert_response :success
402 assert_response :success
403 assert_template 'new'
403 assert_template 'new'
404
404
405 issue = assigns(:issue)
405 issue = assigns(:issue)
406 assert_not_nil issue
406 assert_not_nil issue
407 assert_equal Project.find(1).trackers.first, issue.tracker
407 assert_equal Project.find(1).trackers.first, issue.tracker
408 end
408 end
409
409
410 def test_get_new_with_no_default_status_should_display_an_error
410 def test_get_new_with_no_default_status_should_display_an_error
411 @request.session[:user_id] = 2
411 @request.session[:user_id] = 2
412 IssueStatus.delete_all
412 IssueStatus.delete_all
413
413
414 get :new, :project_id => 1
414 get :new, :project_id => 1
415 assert_response 500
415 assert_response 500
416 assert_not_nil flash[:error]
416 assert_not_nil flash[:error]
417 assert_tag :tag => 'div', :attributes => { :class => /error/ },
417 assert_tag :tag => 'div', :attributes => { :class => /error/ },
418 :content => /No default issue/
418 :content => /No default issue/
419 end
419 end
420
420
421 def test_get_new_with_no_tracker_should_display_an_error
421 def test_get_new_with_no_tracker_should_display_an_error
422 @request.session[:user_id] = 2
422 @request.session[:user_id] = 2
423 Tracker.delete_all
423 Tracker.delete_all
424
424
425 get :new, :project_id => 1
425 get :new, :project_id => 1
426 assert_response 500
426 assert_response 500
427 assert_not_nil flash[:error]
427 assert_not_nil flash[:error]
428 assert_tag :tag => 'div', :attributes => { :class => /error/ },
428 assert_tag :tag => 'div', :attributes => { :class => /error/ },
429 :content => /No tracker/
429 :content => /No tracker/
430 end
430 end
431
431
432 def test_update_new_form
432 def test_update_new_form
433 @request.session[:user_id] = 2
433 @request.session[:user_id] = 2
434 xhr :post, :update_form, :project_id => 1,
434 xhr :post, :update_form, :project_id => 1,
435 :issue => {:tracker_id => 2,
435 :issue => {:tracker_id => 2,
436 :subject => 'This is the test_new issue',
436 :subject => 'This is the test_new issue',
437 :description => 'This is the description',
437 :description => 'This is the description',
438 :priority_id => 5}
438 :priority_id => 5}
439 assert_response :success
439 assert_response :success
440 assert_template 'attributes'
440 assert_template 'attributes'
441
441
442 issue = assigns(:issue)
442 issue = assigns(:issue)
443 assert_kind_of Issue, issue
443 assert_kind_of Issue, issue
444 assert_equal 1, issue.project_id
444 assert_equal 1, issue.project_id
445 assert_equal 2, issue.tracker_id
445 assert_equal 2, issue.tracker_id
446 assert_equal 'This is the test_new issue', issue.subject
446 assert_equal 'This is the test_new issue', issue.subject
447 end
447 end
448
448
449 def test_post_new
449 def test_post_new
450 @request.session[:user_id] = 2
450 @request.session[:user_id] = 2
451 assert_difference 'Issue.count' do
451 assert_difference 'Issue.count' do
452 post :new, :project_id => 1,
452 post :new, :project_id => 1,
453 :issue => {:tracker_id => 3,
453 :issue => {:tracker_id => 3,
454 :subject => 'This is the test_new issue',
454 :subject => 'This is the test_new issue',
455 :description => 'This is the description',
455 :description => 'This is the description',
456 :priority_id => 5,
456 :priority_id => 5,
457 :estimated_hours => '',
457 :estimated_hours => '',
458 :custom_field_values => {'2' => 'Value for field 2'}}
458 :custom_field_values => {'2' => 'Value for field 2'}}
459 end
459 end
460 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
460 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
461
461
462 issue = Issue.find_by_subject('This is the test_new issue')
462 issue = Issue.find_by_subject('This is the test_new issue')
463 assert_not_nil issue
463 assert_not_nil issue
464 assert_equal 2, issue.author_id
464 assert_equal 2, issue.author_id
465 assert_equal 3, issue.tracker_id
465 assert_equal 3, issue.tracker_id
466 assert_nil issue.estimated_hours
466 assert_nil issue.estimated_hours
467 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
467 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
468 assert_not_nil v
468 assert_not_nil v
469 assert_equal 'Value for field 2', v.value
469 assert_equal 'Value for field 2', v.value
470 end
470 end
471
471
472 def test_post_new_and_continue
472 def test_post_new_and_continue
473 @request.session[:user_id] = 2
473 @request.session[:user_id] = 2
474 post :new, :project_id => 1,
474 post :new, :project_id => 1,
475 :issue => {:tracker_id => 3,
475 :issue => {:tracker_id => 3,
476 :subject => 'This is first issue',
476 :subject => 'This is first issue',
477 :priority_id => 5},
477 :priority_id => 5},
478 :continue => ''
478 :continue => ''
479 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
479 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
480 end
480 end
481
481
482 def test_post_new_without_custom_fields_param
482 def test_post_new_without_custom_fields_param
483 @request.session[:user_id] = 2
483 @request.session[:user_id] = 2
484 assert_difference 'Issue.count' do
484 assert_difference 'Issue.count' do
485 post :new, :project_id => 1,
485 post :new, :project_id => 1,
486 :issue => {:tracker_id => 1,
486 :issue => {:tracker_id => 1,
487 :subject => 'This is the test_new issue',
487 :subject => 'This is the test_new issue',
488 :description => 'This is the description',
488 :description => 'This is the description',
489 :priority_id => 5}
489 :priority_id => 5}
490 end
490 end
491 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
491 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
492 end
492 end
493
493
494 def test_post_new_with_required_custom_field_and_without_custom_fields_param
494 def test_post_new_with_required_custom_field_and_without_custom_fields_param
495 field = IssueCustomField.find_by_name('Database')
495 field = IssueCustomField.find_by_name('Database')
496 field.update_attribute(:is_required, true)
496 field.update_attribute(:is_required, true)
497
497
498 @request.session[:user_id] = 2
498 @request.session[:user_id] = 2
499 post :new, :project_id => 1,
499 post :new, :project_id => 1,
500 :issue => {:tracker_id => 1,
500 :issue => {:tracker_id => 1,
501 :subject => 'This is the test_new issue',
501 :subject => 'This is the test_new issue',
502 :description => 'This is the description',
502 :description => 'This is the description',
503 :priority_id => 5}
503 :priority_id => 5}
504 assert_response :success
504 assert_response :success
505 assert_template 'new'
505 assert_template 'new'
506 issue = assigns(:issue)
506 issue = assigns(:issue)
507 assert_not_nil issue
507 assert_not_nil issue
508 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
508 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
509 end
509 end
510
510
511 def test_post_new_with_watchers
511 def test_post_new_with_watchers
512 @request.session[:user_id] = 2
512 @request.session[:user_id] = 2
513 ActionMailer::Base.deliveries.clear
513 ActionMailer::Base.deliveries.clear
514
514
515 assert_difference 'Watcher.count', 2 do
515 assert_difference 'Watcher.count', 2 do
516 post :new, :project_id => 1,
516 post :new, :project_id => 1,
517 :issue => {:tracker_id => 1,
517 :issue => {:tracker_id => 1,
518 :subject => 'This is a new issue with watchers',
518 :subject => 'This is a new issue with watchers',
519 :description => 'This is the description',
519 :description => 'This is the description',
520 :priority_id => 5,
520 :priority_id => 5,
521 :watcher_user_ids => ['2', '3']}
521 :watcher_user_ids => ['2', '3']}
522 end
522 end
523 issue = Issue.find_by_subject('This is a new issue with watchers')
523 issue = Issue.find_by_subject('This is a new issue with watchers')
524 assert_not_nil issue
524 assert_not_nil issue
525 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
525 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
526
526
527 # Watchers added
527 # Watchers added
528 assert_equal [2, 3], issue.watcher_user_ids.sort
528 assert_equal [2, 3], issue.watcher_user_ids.sort
529 assert issue.watched_by?(User.find(3))
529 assert issue.watched_by?(User.find(3))
530 # Watchers notified
530 # Watchers notified
531 mail = ActionMailer::Base.deliveries.last
531 mail = ActionMailer::Base.deliveries.last
532 assert_kind_of TMail::Mail, mail
532 assert_kind_of TMail::Mail, mail
533 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
533 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
534 end
534 end
535
535
536 def test_post_new_should_send_a_notification
536 def test_post_new_should_send_a_notification
537 ActionMailer::Base.deliveries.clear
537 ActionMailer::Base.deliveries.clear
538 @request.session[:user_id] = 2
538 @request.session[:user_id] = 2
539 assert_difference 'Issue.count' do
539 assert_difference 'Issue.count' do
540 post :new, :project_id => 1,
540 post :new, :project_id => 1,
541 :issue => {:tracker_id => 3,
541 :issue => {:tracker_id => 3,
542 :subject => 'This is the test_new issue',
542 :subject => 'This is the test_new issue',
543 :description => 'This is the description',
543 :description => 'This is the description',
544 :priority_id => 5,
544 :priority_id => 5,
545 :estimated_hours => '',
545 :estimated_hours => '',
546 :custom_field_values => {'2' => 'Value for field 2'}}
546 :custom_field_values => {'2' => 'Value for field 2'}}
547 end
547 end
548 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
548 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
549
549
550 assert_equal 1, ActionMailer::Base.deliveries.size
550 assert_equal 1, ActionMailer::Base.deliveries.size
551 end
551 end
552
552
553 def test_post_should_preserve_fields_values_on_validation_failure
553 def test_post_should_preserve_fields_values_on_validation_failure
554 @request.session[:user_id] = 2
554 @request.session[:user_id] = 2
555 post :new, :project_id => 1,
555 post :new, :project_id => 1,
556 :issue => {:tracker_id => 1,
556 :issue => {:tracker_id => 1,
557 # empty subject
557 # empty subject
558 :subject => '',
558 :subject => '',
559 :description => 'This is a description',
559 :description => 'This is a description',
560 :priority_id => 6,
560 :priority_id => 6,
561 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
561 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
562 assert_response :success
562 assert_response :success
563 assert_template 'new'
563 assert_template 'new'
564
564
565 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
565 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
566 :content => 'This is a description'
566 :content => 'This is a description'
567 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
567 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
568 :child => { :tag => 'option', :attributes => { :selected => 'selected',
568 :child => { :tag => 'option', :attributes => { :selected => 'selected',
569 :value => '6' },
569 :value => '6' },
570 :content => 'High' }
570 :content => 'High' }
571 # Custom fields
571 # Custom fields
572 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
572 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
573 :child => { :tag => 'option', :attributes => { :selected => 'selected',
573 :child => { :tag => 'option', :attributes => { :selected => 'selected',
574 :value => 'Oracle' },
574 :value => 'Oracle' },
575 :content => 'Oracle' }
575 :content => 'Oracle' }
576 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
576 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
577 :value => 'Value for field 2'}
577 :value => 'Value for field 2'}
578 end
578 end
579
579
580 def test_post_new_should_ignore_non_safe_attributes
580 def test_post_new_should_ignore_non_safe_attributes
581 @request.session[:user_id] = 2
581 @request.session[:user_id] = 2
582 assert_nothing_raised do
582 assert_nothing_raised do
583 post :new, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
583 post :new, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
584 end
584 end
585 end
585 end
586
586
587 def test_copy_issue
587 def test_copy_issue
588 @request.session[:user_id] = 2
588 @request.session[:user_id] = 2
589 get :new, :project_id => 1, :copy_from => 1
589 get :new, :project_id => 1, :copy_from => 1
590 assert_template 'new'
590 assert_template 'new'
591 assert_not_nil assigns(:issue)
591 assert_not_nil assigns(:issue)
592 orig = Issue.find(1)
592 orig = Issue.find(1)
593 assert_equal orig.subject, assigns(:issue).subject
593 assert_equal orig.subject, assigns(:issue).subject
594 end
594 end
595
595
596 def test_get_edit
596 def test_get_edit
597 @request.session[:user_id] = 2
597 @request.session[:user_id] = 2
598 get :edit, :id => 1
598 get :edit, :id => 1
599 assert_response :success
599 assert_response :success
600 assert_template 'edit'
600 assert_template 'edit'
601 assert_not_nil assigns(:issue)
601 assert_not_nil assigns(:issue)
602 assert_equal Issue.find(1), assigns(:issue)
602 assert_equal Issue.find(1), assigns(:issue)
603 end
603 end
604
604
605 def test_get_edit_with_params
605 def test_get_edit_with_params
606 @request.session[:user_id] = 2
606 @request.session[:user_id] = 2
607 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
607 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
608 assert_response :success
608 assert_response :success
609 assert_template 'edit'
609 assert_template 'edit'
610
610
611 issue = assigns(:issue)
611 issue = assigns(:issue)
612 assert_not_nil issue
612 assert_not_nil issue
613
613
614 assert_equal 5, issue.status_id
614 assert_equal 5, issue.status_id
615 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
615 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
616 :child => { :tag => 'option',
616 :child => { :tag => 'option',
617 :content => 'Closed',
617 :content => 'Closed',
618 :attributes => { :selected => 'selected' } }
618 :attributes => { :selected => 'selected' } }
619
619
620 assert_equal 7, issue.priority_id
620 assert_equal 7, issue.priority_id
621 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
621 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
622 :child => { :tag => 'option',
622 :child => { :tag => 'option',
623 :content => 'Urgent',
623 :content => 'Urgent',
624 :attributes => { :selected => 'selected' } }
624 :attributes => { :selected => 'selected' } }
625 end
625 end
626
626
627 def test_update_edit_form
627 def test_update_edit_form
628 @request.session[:user_id] = 2
628 @request.session[:user_id] = 2
629 xhr :post, :update_form, :project_id => 1,
629 xhr :post, :update_form, :project_id => 1,
630 :id => 1,
630 :id => 1,
631 :issue => {:tracker_id => 2,
631 :issue => {:tracker_id => 2,
632 :subject => 'This is the test_new issue',
632 :subject => 'This is the test_new issue',
633 :description => 'This is the description',
633 :description => 'This is the description',
634 :priority_id => 5}
634 :priority_id => 5}
635 assert_response :success
635 assert_response :success
636 assert_template 'attributes'
636 assert_template 'attributes'
637
637
638 issue = assigns(:issue)
638 issue = assigns(:issue)
639 assert_kind_of Issue, issue
639 assert_kind_of Issue, issue
640 assert_equal 1, issue.id
640 assert_equal 1, issue.id
641 assert_equal 1, issue.project_id
641 assert_equal 1, issue.project_id
642 assert_equal 2, issue.tracker_id
642 assert_equal 2, issue.tracker_id
643 assert_equal 'This is the test_new issue', issue.subject
643 assert_equal 'This is the test_new issue', issue.subject
644 end
644 end
645
645
646 def test_reply_to_issue
646 def test_reply_to_issue
647 @request.session[:user_id] = 2
647 @request.session[:user_id] = 2
648 get :reply, :id => 1
648 get :reply, :id => 1
649 assert_response :success
649 assert_response :success
650 assert_select_rjs :show, "update"
650 assert_select_rjs :show, "update"
651 end
651 end
652
652
653 def test_reply_to_note
653 def test_reply_to_note
654 @request.session[:user_id] = 2
654 @request.session[:user_id] = 2
655 get :reply, :id => 1, :journal_id => 2
655 get :reply, :id => 1, :journal_id => 2
656 assert_response :success
656 assert_response :success
657 assert_select_rjs :show, "update"
657 assert_select_rjs :show, "update"
658 end
658 end
659
659
660 def test_post_edit_without_custom_fields_param
660 def test_put_update_without_custom_fields_param
661 @request.session[:user_id] = 2
661 @request.session[:user_id] = 2
662 ActionMailer::Base.deliveries.clear
662 ActionMailer::Base.deliveries.clear
663
663
664 issue = Issue.find(1)
664 issue = Issue.find(1)
665 assert_equal '125', issue.custom_value_for(2).value
665 assert_equal '125', issue.custom_value_for(2).value
666 old_subject = issue.subject
666 old_subject = issue.subject
667 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
667 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
668
668
669 assert_difference('Journal.count') do
669 assert_difference('Journal.count') do
670 assert_difference('JournalDetail.count', 2) do
670 assert_difference('JournalDetail.count', 2) do
671 post :edit, :id => 1, :issue => {:subject => new_subject,
671 put :update, :id => 1, :issue => {:subject => new_subject,
672 :priority_id => '6',
672 :priority_id => '6',
673 :category_id => '1' # no change
673 :category_id => '1' # no change
674 }
674 }
675 end
675 end
676 end
676 end
677 assert_redirected_to :action => 'show', :id => '1'
677 assert_redirected_to :action => 'show', :id => '1'
678 issue.reload
678 issue.reload
679 assert_equal new_subject, issue.subject
679 assert_equal new_subject, issue.subject
680 # Make sure custom fields were not cleared
680 # Make sure custom fields were not cleared
681 assert_equal '125', issue.custom_value_for(2).value
681 assert_equal '125', issue.custom_value_for(2).value
682
682
683 mail = ActionMailer::Base.deliveries.last
683 mail = ActionMailer::Base.deliveries.last
684 assert_kind_of TMail::Mail, mail
684 assert_kind_of TMail::Mail, mail
685 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
685 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
686 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
686 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
687 end
687 end
688
688
689 def test_post_edit_with_custom_field_change
689 def test_put_update_with_custom_field_change
690 @request.session[:user_id] = 2
690 @request.session[:user_id] = 2
691 issue = Issue.find(1)
691 issue = Issue.find(1)
692 assert_equal '125', issue.custom_value_for(2).value
692 assert_equal '125', issue.custom_value_for(2).value
693
693
694 assert_difference('Journal.count') do
694 assert_difference('Journal.count') do
695 assert_difference('JournalDetail.count', 3) do
695 assert_difference('JournalDetail.count', 3) do
696 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
696 put :update, :id => 1, :issue => {:subject => 'Custom field change',
697 :priority_id => '6',
697 :priority_id => '6',
698 :category_id => '1', # no change
698 :category_id => '1', # no change
699 :custom_field_values => { '2' => 'New custom value' }
699 :custom_field_values => { '2' => 'New custom value' }
700 }
700 }
701 end
701 end
702 end
702 end
703 assert_redirected_to :action => 'show', :id => '1'
703 assert_redirected_to :action => 'show', :id => '1'
704 issue.reload
704 issue.reload
705 assert_equal 'New custom value', issue.custom_value_for(2).value
705 assert_equal 'New custom value', issue.custom_value_for(2).value
706
706
707 mail = ActionMailer::Base.deliveries.last
707 mail = ActionMailer::Base.deliveries.last
708 assert_kind_of TMail::Mail, mail
708 assert_kind_of TMail::Mail, mail
709 assert mail.body.include?("Searchable field changed from 125 to New custom value")
709 assert mail.body.include?("Searchable field changed from 125 to New custom value")
710 end
710 end
711
711
712 def test_post_edit_with_status_and_assignee_change
712 def test_put_update_with_status_and_assignee_change
713 issue = Issue.find(1)
713 issue = Issue.find(1)
714 assert_equal 1, issue.status_id
714 assert_equal 1, issue.status_id
715 @request.session[:user_id] = 2
715 @request.session[:user_id] = 2
716 assert_difference('TimeEntry.count', 0) do
716 assert_difference('TimeEntry.count', 0) do
717 post :edit,
717 put :update,
718 :id => 1,
718 :id => 1,
719 :issue => { :status_id => 2, :assigned_to_id => 3 },
719 :issue => { :status_id => 2, :assigned_to_id => 3 },
720 :notes => 'Assigned to dlopper',
720 :notes => 'Assigned to dlopper',
721 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
721 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
722 end
722 end
723 assert_redirected_to :action => 'show', :id => '1'
723 assert_redirected_to :action => 'show', :id => '1'
724 issue.reload
724 issue.reload
725 assert_equal 2, issue.status_id
725 assert_equal 2, issue.status_id
726 j = Journal.find(:first, :order => 'id DESC')
726 j = Journal.find(:first, :order => 'id DESC')
727 assert_equal 'Assigned to dlopper', j.notes
727 assert_equal 'Assigned to dlopper', j.notes
728 assert_equal 2, j.details.size
728 assert_equal 2, j.details.size
729
729
730 mail = ActionMailer::Base.deliveries.last
730 mail = ActionMailer::Base.deliveries.last
731 assert mail.body.include?("Status changed from New to Assigned")
731 assert mail.body.include?("Status changed from New to Assigned")
732 # subject should contain the new status
732 # subject should contain the new status
733 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
733 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
734 end
734 end
735
735
736 def test_post_edit_with_note_only
736 def test_put_update_with_note_only
737 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
737 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
738 # anonymous user
738 # anonymous user
739 post :edit,
739 put :update,
740 :id => 1,
740 :id => 1,
741 :notes => notes
741 :notes => notes
742 assert_redirected_to :action => 'show', :id => '1'
742 assert_redirected_to :action => 'show', :id => '1'
743 j = Journal.find(:first, :order => 'id DESC')
743 j = Journal.find(:first, :order => 'id DESC')
744 assert_equal notes, j.notes
744 assert_equal notes, j.notes
745 assert_equal 0, j.details.size
745 assert_equal 0, j.details.size
746 assert_equal User.anonymous, j.user
746 assert_equal User.anonymous, j.user
747
747
748 mail = ActionMailer::Base.deliveries.last
748 mail = ActionMailer::Base.deliveries.last
749 assert mail.body.include?(notes)
749 assert mail.body.include?(notes)
750 end
750 end
751
751
752 def test_post_edit_with_note_and_spent_time
752 def test_put_update_with_note_and_spent_time
753 @request.session[:user_id] = 2
753 @request.session[:user_id] = 2
754 spent_hours_before = Issue.find(1).spent_hours
754 spent_hours_before = Issue.find(1).spent_hours
755 assert_difference('TimeEntry.count') do
755 assert_difference('TimeEntry.count') do
756 post :edit,
756 put :update,
757 :id => 1,
757 :id => 1,
758 :notes => '2.5 hours added',
758 :notes => '2.5 hours added',
759 :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
759 :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
760 end
760 end
761 assert_redirected_to :action => 'show', :id => '1'
761 assert_redirected_to :action => 'show', :id => '1'
762
762
763 issue = Issue.find(1)
763 issue = Issue.find(1)
764
764
765 j = Journal.find(:first, :order => 'id DESC')
765 j = Journal.find(:first, :order => 'id DESC')
766 assert_equal '2.5 hours added', j.notes
766 assert_equal '2.5 hours added', j.notes
767 assert_equal 0, j.details.size
767 assert_equal 0, j.details.size
768
768
769 t = issue.time_entries.find(:first, :order => 'id DESC')
769 t = issue.time_entries.find(:first, :order => 'id DESC')
770 assert_not_nil t
770 assert_not_nil t
771 assert_equal 2.5, t.hours
771 assert_equal 2.5, t.hours
772 assert_equal spent_hours_before + 2.5, issue.spent_hours
772 assert_equal spent_hours_before + 2.5, issue.spent_hours
773 end
773 end
774
774
775 def test_post_edit_with_attachment_only
775 def test_put_update_with_attachment_only
776 set_tmp_attachments_directory
776 set_tmp_attachments_directory
777
777
778 # Delete all fixtured journals, a race condition can occur causing the wrong
778 # Delete all fixtured journals, a race condition can occur causing the wrong
779 # journal to get fetched in the next find.
779 # journal to get fetched in the next find.
780 Journal.delete_all
780 Journal.delete_all
781
781
782 # anonymous user
782 # anonymous user
783 post :edit,
783 put :update,
784 :id => 1,
784 :id => 1,
785 :notes => '',
785 :notes => '',
786 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
786 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
787 assert_redirected_to :action => 'show', :id => '1'
787 assert_redirected_to :action => 'show', :id => '1'
788 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
788 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
789 assert j.notes.blank?
789 assert j.notes.blank?
790 assert_equal 1, j.details.size
790 assert_equal 1, j.details.size
791 assert_equal 'testfile.txt', j.details.first.value
791 assert_equal 'testfile.txt', j.details.first.value
792 assert_equal User.anonymous, j.user
792 assert_equal User.anonymous, j.user
793
793
794 mail = ActionMailer::Base.deliveries.last
794 mail = ActionMailer::Base.deliveries.last
795 assert mail.body.include?('testfile.txt')
795 assert mail.body.include?('testfile.txt')
796 end
796 end
797
797
798 def test_post_edit_with_no_change
798 def test_put_update_with_no_change
799 issue = Issue.find(1)
799 issue = Issue.find(1)
800 issue.journals.clear
800 issue.journals.clear
801 ActionMailer::Base.deliveries.clear
801 ActionMailer::Base.deliveries.clear
802
802
803 post :edit,
803 put :update,
804 :id => 1,
804 :id => 1,
805 :notes => ''
805 :notes => ''
806 assert_redirected_to :action => 'show', :id => '1'
806 assert_redirected_to :action => 'show', :id => '1'
807
807
808 issue.reload
808 issue.reload
809 assert issue.journals.empty?
809 assert issue.journals.empty?
810 # No email should be sent
810 # No email should be sent
811 assert ActionMailer::Base.deliveries.empty?
811 assert ActionMailer::Base.deliveries.empty?
812 end
812 end
813
813
814 def test_post_edit_should_send_a_notification
814 def test_put_update_should_send_a_notification
815 @request.session[:user_id] = 2
815 @request.session[:user_id] = 2
816 ActionMailer::Base.deliveries.clear
816 ActionMailer::Base.deliveries.clear
817 issue = Issue.find(1)
817 issue = Issue.find(1)
818 old_subject = issue.subject
818 old_subject = issue.subject
819 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
819 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
820
820
821 post :edit, :id => 1, :issue => {:subject => new_subject,
821 put :update, :id => 1, :issue => {:subject => new_subject,
822 :priority_id => '6',
822 :priority_id => '6',
823 :category_id => '1' # no change
823 :category_id => '1' # no change
824 }
824 }
825 assert_equal 1, ActionMailer::Base.deliveries.size
825 assert_equal 1, ActionMailer::Base.deliveries.size
826 end
826 end
827
827
828 def test_post_edit_with_invalid_spent_time
828 def test_put_update_with_invalid_spent_time
829 @request.session[:user_id] = 2
829 @request.session[:user_id] = 2
830 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
830 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
831
831
832 assert_no_difference('Journal.count') do
832 assert_no_difference('Journal.count') do
833 post :edit,
833 put :update,
834 :id => 1,
834 :id => 1,
835 :notes => notes,
835 :notes => notes,
836 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
836 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
837 end
837 end
838 assert_response :success
838 assert_response :success
839 assert_template 'edit'
839 assert_template 'edit'
840
840
841 assert_tag :textarea, :attributes => { :name => 'notes' },
841 assert_tag :textarea, :attributes => { :name => 'notes' },
842 :content => notes
842 :content => notes
843 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
843 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
844 end
844 end
845
845
846 def test_post_edit_should_allow_fixed_version_to_be_set_to_a_subproject
846 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
847 issue = Issue.find(2)
847 issue = Issue.find(2)
848 @request.session[:user_id] = 2
848 @request.session[:user_id] = 2
849
849
850 post :edit,
850 put :update,
851 :id => issue.id,
851 :id => issue.id,
852 :issue => {
852 :issue => {
853 :fixed_version_id => 4
853 :fixed_version_id => 4
854 }
854 }
855
855
856 assert_response :redirect
856 assert_response :redirect
857 issue.reload
857 issue.reload
858 assert_equal 4, issue.fixed_version_id
858 assert_equal 4, issue.fixed_version_id
859 assert_not_equal issue.project_id, issue.fixed_version.project_id
859 assert_not_equal issue.project_id, issue.fixed_version.project_id
860 end
860 end
861
861
862 def test_post_edit_should_redirect_back_using_the_back_url_parameter
862 def test_put_update_should_redirect_back_using_the_back_url_parameter
863 issue = Issue.find(2)
863 issue = Issue.find(2)
864 @request.session[:user_id] = 2
864 @request.session[:user_id] = 2
865
865
866 post :edit,
866 put :update,
867 :id => issue.id,
867 :id => issue.id,
868 :issue => {
868 :issue => {
869 :fixed_version_id => 4
869 :fixed_version_id => 4
870 },
870 },
871 :back_url => '/issues'
871 :back_url => '/issues'
872
872
873 assert_response :redirect
873 assert_response :redirect
874 assert_redirected_to '/issues'
874 assert_redirected_to '/issues'
875 end
875 end
876
876
877 def test_post_edit_should_not_redirect_back_using_the_back_url_parameter_off_the_host
877 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
878 issue = Issue.find(2)
878 issue = Issue.find(2)
879 @request.session[:user_id] = 2
879 @request.session[:user_id] = 2
880
880
881 post :edit,
881 put :update,
882 :id => issue.id,
882 :id => issue.id,
883 :issue => {
883 :issue => {
884 :fixed_version_id => 4
884 :fixed_version_id => 4
885 },
885 },
886 :back_url => 'http://google.com'
886 :back_url => 'http://google.com'
887
887
888 assert_response :redirect
888 assert_response :redirect
889 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
889 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
890 end
890 end
891
891
892 def test_get_bulk_edit
892 def test_get_bulk_edit
893 @request.session[:user_id] = 2
893 @request.session[:user_id] = 2
894 get :bulk_edit, :ids => [1, 2]
894 get :bulk_edit, :ids => [1, 2]
895 assert_response :success
895 assert_response :success
896 assert_template 'bulk_edit'
896 assert_template 'bulk_edit'
897
897
898 # Project specific custom field, date type
898 # Project specific custom field, date type
899 field = CustomField.find(9)
899 field = CustomField.find(9)
900 assert !field.is_for_all?
900 assert !field.is_for_all?
901 assert_equal 'date', field.field_format
901 assert_equal 'date', field.field_format
902 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
902 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
903
903
904 # System wide custom field
904 # System wide custom field
905 assert CustomField.find(1).is_for_all?
905 assert CustomField.find(1).is_for_all?
906 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
906 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
907 end
907 end
908
908
909 def test_bulk_edit
909 def test_bulk_edit
910 @request.session[:user_id] = 2
910 @request.session[:user_id] = 2
911 # update issues priority
911 # update issues priority
912 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing',
912 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing',
913 :issue => {:priority_id => 7,
913 :issue => {:priority_id => 7,
914 :assigned_to_id => '',
914 :assigned_to_id => '',
915 :custom_field_values => {'2' => ''}}
915 :custom_field_values => {'2' => ''}}
916
916
917 assert_response 302
917 assert_response 302
918 # check that the issues were updated
918 # check that the issues were updated
919 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
919 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
920
920
921 issue = Issue.find(1)
921 issue = Issue.find(1)
922 journal = issue.journals.find(:first, :order => 'created_on DESC')
922 journal = issue.journals.find(:first, :order => 'created_on DESC')
923 assert_equal '125', issue.custom_value_for(2).value
923 assert_equal '125', issue.custom_value_for(2).value
924 assert_equal 'Bulk editing', journal.notes
924 assert_equal 'Bulk editing', journal.notes
925 assert_equal 1, journal.details.size
925 assert_equal 1, journal.details.size
926 end
926 end
927
927
928 def test_bullk_edit_should_send_a_notification
928 def test_bullk_edit_should_send_a_notification
929 @request.session[:user_id] = 2
929 @request.session[:user_id] = 2
930 ActionMailer::Base.deliveries.clear
930 ActionMailer::Base.deliveries.clear
931 post(:bulk_edit,
931 post(:bulk_edit,
932 {
932 {
933 :ids => [1, 2],
933 :ids => [1, 2],
934 :notes => 'Bulk editing',
934 :notes => 'Bulk editing',
935 :issue => {
935 :issue => {
936 :priority_id => 7,
936 :priority_id => 7,
937 :assigned_to_id => '',
937 :assigned_to_id => '',
938 :custom_field_values => {'2' => ''}
938 :custom_field_values => {'2' => ''}
939 }
939 }
940 })
940 })
941
941
942 assert_response 302
942 assert_response 302
943 assert_equal 2, ActionMailer::Base.deliveries.size
943 assert_equal 2, ActionMailer::Base.deliveries.size
944 end
944 end
945
945
946 def test_bulk_edit_status
946 def test_bulk_edit_status
947 @request.session[:user_id] = 2
947 @request.session[:user_id] = 2
948 # update issues priority
948 # update issues priority
949 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing status',
949 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing status',
950 :issue => {:priority_id => '',
950 :issue => {:priority_id => '',
951 :assigned_to_id => '',
951 :assigned_to_id => '',
952 :status_id => '5'}
952 :status_id => '5'}
953
953
954 assert_response 302
954 assert_response 302
955 issue = Issue.find(1)
955 issue = Issue.find(1)
956 assert issue.closed?
956 assert issue.closed?
957 end
957 end
958
958
959 def test_bulk_edit_custom_field
959 def test_bulk_edit_custom_field
960 @request.session[:user_id] = 2
960 @request.session[:user_id] = 2
961 # update issues priority
961 # update issues priority
962 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing custom field',
962 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing custom field',
963 :issue => {:priority_id => '',
963 :issue => {:priority_id => '',
964 :assigned_to_id => '',
964 :assigned_to_id => '',
965 :custom_field_values => {'2' => '777'}}
965 :custom_field_values => {'2' => '777'}}
966
966
967 assert_response 302
967 assert_response 302
968
968
969 issue = Issue.find(1)
969 issue = Issue.find(1)
970 journal = issue.journals.find(:first, :order => 'created_on DESC')
970 journal = issue.journals.find(:first, :order => 'created_on DESC')
971 assert_equal '777', issue.custom_value_for(2).value
971 assert_equal '777', issue.custom_value_for(2).value
972 assert_equal 1, journal.details.size
972 assert_equal 1, journal.details.size
973 assert_equal '125', journal.details.first.old_value
973 assert_equal '125', journal.details.first.old_value
974 assert_equal '777', journal.details.first.value
974 assert_equal '777', journal.details.first.value
975 end
975 end
976
976
977 def test_bulk_unassign
977 def test_bulk_unassign
978 assert_not_nil Issue.find(2).assigned_to
978 assert_not_nil Issue.find(2).assigned_to
979 @request.session[:user_id] = 2
979 @request.session[:user_id] = 2
980 # unassign issues
980 # unassign issues
981 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
981 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
982 assert_response 302
982 assert_response 302
983 # check that the issues were updated
983 # check that the issues were updated
984 assert_nil Issue.find(2).assigned_to
984 assert_nil Issue.find(2).assigned_to
985 end
985 end
986
986
987 def test_post_bulk_edit_should_allow_fixed_version_to_be_set_to_a_subproject
987 def test_post_bulk_edit_should_allow_fixed_version_to_be_set_to_a_subproject
988 @request.session[:user_id] = 2
988 @request.session[:user_id] = 2
989
989
990 post :bulk_edit, :ids => [1,2], :issue => {:fixed_version_id => 4}
990 post :bulk_edit, :ids => [1,2], :issue => {:fixed_version_id => 4}
991
991
992 assert_response :redirect
992 assert_response :redirect
993 issues = Issue.find([1,2])
993 issues = Issue.find([1,2])
994 issues.each do |issue|
994 issues.each do |issue|
995 assert_equal 4, issue.fixed_version_id
995 assert_equal 4, issue.fixed_version_id
996 assert_not_equal issue.project_id, issue.fixed_version.project_id
996 assert_not_equal issue.project_id, issue.fixed_version.project_id
997 end
997 end
998 end
998 end
999
999
1000 def test_post_bulk_edit_should_redirect_back_using_the_back_url_parameter
1000 def test_post_bulk_edit_should_redirect_back_using_the_back_url_parameter
1001 @request.session[:user_id] = 2
1001 @request.session[:user_id] = 2
1002 post :bulk_edit, :ids => [1,2], :back_url => '/issues'
1002 post :bulk_edit, :ids => [1,2], :back_url => '/issues'
1003
1003
1004 assert_response :redirect
1004 assert_response :redirect
1005 assert_redirected_to '/issues'
1005 assert_redirected_to '/issues'
1006 end
1006 end
1007
1007
1008 def test_post_bulk_edit_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1008 def test_post_bulk_edit_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1009 @request.session[:user_id] = 2
1009 @request.session[:user_id] = 2
1010 post :bulk_edit, :ids => [1,2], :back_url => 'http://google.com'
1010 post :bulk_edit, :ids => [1,2], :back_url => 'http://google.com'
1011
1011
1012 assert_response :redirect
1012 assert_response :redirect
1013 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1013 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1014 end
1014 end
1015
1015
1016 def test_move_one_issue_to_another_project
1016 def test_move_one_issue_to_another_project
1017 @request.session[:user_id] = 2
1017 @request.session[:user_id] = 2
1018 post :move, :id => 1, :new_project_id => 2, :tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1018 post :move, :id => 1, :new_project_id => 2, :tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1019 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1019 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1020 assert_equal 2, Issue.find(1).project_id
1020 assert_equal 2, Issue.find(1).project_id
1021 end
1021 end
1022
1022
1023 def test_move_one_issue_to_another_project_should_follow_when_needed
1023 def test_move_one_issue_to_another_project_should_follow_when_needed
1024 @request.session[:user_id] = 2
1024 @request.session[:user_id] = 2
1025 post :move, :id => 1, :new_project_id => 2, :follow => '1'
1025 post :move, :id => 1, :new_project_id => 2, :follow => '1'
1026 assert_redirected_to '/issues/1'
1026 assert_redirected_to '/issues/1'
1027 end
1027 end
1028
1028
1029 def test_bulk_move_to_another_project
1029 def test_bulk_move_to_another_project
1030 @request.session[:user_id] = 2
1030 @request.session[:user_id] = 2
1031 post :move, :ids => [1, 2], :new_project_id => 2
1031 post :move, :ids => [1, 2], :new_project_id => 2
1032 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1032 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1033 # Issues moved to project 2
1033 # Issues moved to project 2
1034 assert_equal 2, Issue.find(1).project_id
1034 assert_equal 2, Issue.find(1).project_id
1035 assert_equal 2, Issue.find(2).project_id
1035 assert_equal 2, Issue.find(2).project_id
1036 # No tracker change
1036 # No tracker change
1037 assert_equal 1, Issue.find(1).tracker_id
1037 assert_equal 1, Issue.find(1).tracker_id
1038 assert_equal 2, Issue.find(2).tracker_id
1038 assert_equal 2, Issue.find(2).tracker_id
1039 end
1039 end
1040
1040
1041 def test_bulk_move_to_another_tracker
1041 def test_bulk_move_to_another_tracker
1042 @request.session[:user_id] = 2
1042 @request.session[:user_id] = 2
1043 post :move, :ids => [1, 2], :new_tracker_id => 2
1043 post :move, :ids => [1, 2], :new_tracker_id => 2
1044 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1044 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1045 assert_equal 2, Issue.find(1).tracker_id
1045 assert_equal 2, Issue.find(1).tracker_id
1046 assert_equal 2, Issue.find(2).tracker_id
1046 assert_equal 2, Issue.find(2).tracker_id
1047 end
1047 end
1048
1048
1049 def test_bulk_copy_to_another_project
1049 def test_bulk_copy_to_another_project
1050 @request.session[:user_id] = 2
1050 @request.session[:user_id] = 2
1051 assert_difference 'Issue.count', 2 do
1051 assert_difference 'Issue.count', 2 do
1052 assert_no_difference 'Project.find(1).issues.count' do
1052 assert_no_difference 'Project.find(1).issues.count' do
1053 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
1053 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
1054 end
1054 end
1055 end
1055 end
1056 assert_redirected_to 'projects/ecookbook/issues'
1056 assert_redirected_to 'projects/ecookbook/issues'
1057 end
1057 end
1058
1058
1059 context "#move via bulk copy" do
1059 context "#move via bulk copy" do
1060 should "allow not changing the issue's attributes" do
1060 should "allow not changing the issue's attributes" do
1061 @request.session[:user_id] = 2
1061 @request.session[:user_id] = 2
1062 issue_before_move = Issue.find(1)
1062 issue_before_move = Issue.find(1)
1063 assert_difference 'Issue.count', 1 do
1063 assert_difference 'Issue.count', 1 do
1064 assert_no_difference 'Project.find(1).issues.count' do
1064 assert_no_difference 'Project.find(1).issues.count' do
1065 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :new_tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1065 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :new_tracker_id => '', :assigned_to_id => '', :status_id => '', :start_date => '', :due_date => ''
1066 end
1066 end
1067 end
1067 end
1068 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
1068 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
1069 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
1069 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
1070 assert_equal issue_before_move.status_id, issue_after_move.status_id
1070 assert_equal issue_before_move.status_id, issue_after_move.status_id
1071 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
1071 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
1072 end
1072 end
1073
1073
1074 should "allow changing the issue's attributes" do
1074 should "allow changing the issue's attributes" do
1075 @request.session[:user_id] = 2
1075 @request.session[:user_id] = 2
1076 assert_difference 'Issue.count', 2 do
1076 assert_difference 'Issue.count', 2 do
1077 assert_no_difference 'Project.find(1).issues.count' do
1077 assert_no_difference 'Project.find(1).issues.count' do
1078 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'
1078 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'
1079 end
1079 end
1080 end
1080 end
1081
1081
1082 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
1082 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
1083 assert_equal 2, copied_issues.size
1083 assert_equal 2, copied_issues.size
1084 copied_issues.each do |issue|
1084 copied_issues.each do |issue|
1085 assert_equal 2, issue.project_id, "Project is incorrect"
1085 assert_equal 2, issue.project_id, "Project is incorrect"
1086 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
1086 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
1087 assert_equal 3, issue.status_id, "Status is incorrect"
1087 assert_equal 3, issue.status_id, "Status is incorrect"
1088 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
1088 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
1089 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
1089 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
1090 end
1090 end
1091 end
1091 end
1092 end
1092 end
1093
1093
1094 def test_copy_to_another_project_should_follow_when_needed
1094 def test_copy_to_another_project_should_follow_when_needed
1095 @request.session[:user_id] = 2
1095 @request.session[:user_id] = 2
1096 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :follow => '1'
1096 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :follow => '1'
1097 issue = Issue.first(:order => 'id DESC')
1097 issue = Issue.first(:order => 'id DESC')
1098 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1098 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1099 end
1099 end
1100
1100
1101 def test_context_menu_one_issue
1101 def test_context_menu_one_issue
1102 @request.session[:user_id] = 2
1102 @request.session[:user_id] = 2
1103 get :context_menu, :ids => [1]
1103 get :context_menu, :ids => [1]
1104 assert_response :success
1104 assert_response :success
1105 assert_template 'context_menu'
1105 assert_template 'context_menu'
1106 assert_tag :tag => 'a', :content => 'Edit',
1106 assert_tag :tag => 'a', :content => 'Edit',
1107 :attributes => { :href => '/issues/1/edit',
1107 :attributes => { :href => '/issues/1/edit',
1108 :class => 'icon-edit' }
1108 :class => 'icon-edit' }
1109 assert_tag :tag => 'a', :content => 'Closed',
1109 assert_tag :tag => 'a', :content => 'Closed',
1110 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
1110 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
1111 :class => '' }
1111 :class => '' }
1112 assert_tag :tag => 'a', :content => 'Immediate',
1112 assert_tag :tag => 'a', :content => 'Immediate',
1113 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1113 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1114 :class => '' }
1114 :class => '' }
1115 # Versions
1115 # Versions
1116 assert_tag :tag => 'a', :content => '2.0',
1116 assert_tag :tag => 'a', :content => '2.0',
1117 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=3&amp;ids%5B%5D=1',
1117 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=3&amp;ids%5B%5D=1',
1118 :class => '' }
1118 :class => '' }
1119 assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0',
1119 assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0',
1120 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=4&amp;ids%5B%5D=1',
1120 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=4&amp;ids%5B%5D=1',
1121 :class => '' }
1121 :class => '' }
1122
1122
1123 assert_tag :tag => 'a', :content => 'Dave Lopper',
1123 assert_tag :tag => 'a', :content => 'Dave Lopper',
1124 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1124 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1125 :class => '' }
1125 :class => '' }
1126 assert_tag :tag => 'a', :content => 'Duplicate',
1126 assert_tag :tag => 'a', :content => 'Duplicate',
1127 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
1127 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
1128 :class => 'icon-duplicate' }
1128 :class => 'icon-duplicate' }
1129 assert_tag :tag => 'a', :content => 'Copy',
1129 assert_tag :tag => 'a', :content => 'Copy',
1130 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1',
1130 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1',
1131 :class => 'icon-copy' }
1131 :class => 'icon-copy' }
1132 assert_tag :tag => 'a', :content => 'Move',
1132 assert_tag :tag => 'a', :content => 'Move',
1133 :attributes => { :href => '/issues/move?ids%5B%5D=1',
1133 :attributes => { :href => '/issues/move?ids%5B%5D=1',
1134 :class => 'icon-move' }
1134 :class => 'icon-move' }
1135 assert_tag :tag => 'a', :content => 'Delete',
1135 assert_tag :tag => 'a', :content => 'Delete',
1136 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
1136 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
1137 :class => 'icon-del' }
1137 :class => 'icon-del' }
1138 end
1138 end
1139
1139
1140 def test_context_menu_one_issue_by_anonymous
1140 def test_context_menu_one_issue_by_anonymous
1141 get :context_menu, :ids => [1]
1141 get :context_menu, :ids => [1]
1142 assert_response :success
1142 assert_response :success
1143 assert_template 'context_menu'
1143 assert_template 'context_menu'
1144 assert_tag :tag => 'a', :content => 'Delete',
1144 assert_tag :tag => 'a', :content => 'Delete',
1145 :attributes => { :href => '#',
1145 :attributes => { :href => '#',
1146 :class => 'icon-del disabled' }
1146 :class => 'icon-del disabled' }
1147 end
1147 end
1148
1148
1149 def test_context_menu_multiple_issues_of_same_project
1149 def test_context_menu_multiple_issues_of_same_project
1150 @request.session[:user_id] = 2
1150 @request.session[:user_id] = 2
1151 get :context_menu, :ids => [1, 2]
1151 get :context_menu, :ids => [1, 2]
1152 assert_response :success
1152 assert_response :success
1153 assert_template 'context_menu'
1153 assert_template 'context_menu'
1154 assert_tag :tag => 'a', :content => 'Edit',
1154 assert_tag :tag => 'a', :content => 'Edit',
1155 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
1155 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
1156 :class => 'icon-edit' }
1156 :class => 'icon-edit' }
1157 assert_tag :tag => 'a', :content => 'Immediate',
1157 assert_tag :tag => 'a', :content => 'Immediate',
1158 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
1158 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
1159 :class => '' }
1159 :class => '' }
1160 assert_tag :tag => 'a', :content => 'Dave Lopper',
1160 assert_tag :tag => 'a', :content => 'Dave Lopper',
1161 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1161 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1162 :class => '' }
1162 :class => '' }
1163 assert_tag :tag => 'a', :content => 'Copy',
1163 assert_tag :tag => 'a', :content => 'Copy',
1164 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1164 :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1165 :class => 'icon-copy' }
1165 :class => 'icon-copy' }
1166 assert_tag :tag => 'a', :content => 'Move',
1166 assert_tag :tag => 'a', :content => 'Move',
1167 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
1167 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
1168 :class => 'icon-move' }
1168 :class => 'icon-move' }
1169 assert_tag :tag => 'a', :content => 'Delete',
1169 assert_tag :tag => 'a', :content => 'Delete',
1170 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
1170 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
1171 :class => 'icon-del' }
1171 :class => 'icon-del' }
1172 end
1172 end
1173
1173
1174 def test_context_menu_multiple_issues_of_different_project
1174 def test_context_menu_multiple_issues_of_different_project
1175 @request.session[:user_id] = 2
1175 @request.session[:user_id] = 2
1176 get :context_menu, :ids => [1, 2, 4]
1176 get :context_menu, :ids => [1, 2, 4]
1177 assert_response :success
1177 assert_response :success
1178 assert_template 'context_menu'
1178 assert_template 'context_menu'
1179 assert_tag :tag => 'a', :content => 'Delete',
1179 assert_tag :tag => 'a', :content => 'Delete',
1180 :attributes => { :href => '#',
1180 :attributes => { :href => '#',
1181 :class => 'icon-del disabled' }
1181 :class => 'icon-del disabled' }
1182 end
1182 end
1183
1183
1184 def test_destroy_issue_with_no_time_entries
1184 def test_destroy_issue_with_no_time_entries
1185 assert_nil TimeEntry.find_by_issue_id(2)
1185 assert_nil TimeEntry.find_by_issue_id(2)
1186 @request.session[:user_id] = 2
1186 @request.session[:user_id] = 2
1187 post :destroy, :id => 2
1187 post :destroy, :id => 2
1188 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1188 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1189 assert_nil Issue.find_by_id(2)
1189 assert_nil Issue.find_by_id(2)
1190 end
1190 end
1191
1191
1192 def test_destroy_issues_with_time_entries
1192 def test_destroy_issues_with_time_entries
1193 @request.session[:user_id] = 2
1193 @request.session[:user_id] = 2
1194 post :destroy, :ids => [1, 3]
1194 post :destroy, :ids => [1, 3]
1195 assert_response :success
1195 assert_response :success
1196 assert_template 'destroy'
1196 assert_template 'destroy'
1197 assert_not_nil assigns(:hours)
1197 assert_not_nil assigns(:hours)
1198 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1198 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1199 end
1199 end
1200
1200
1201 def test_destroy_issues_and_destroy_time_entries
1201 def test_destroy_issues_and_destroy_time_entries
1202 @request.session[:user_id] = 2
1202 @request.session[:user_id] = 2
1203 post :destroy, :ids => [1, 3], :todo => 'destroy'
1203 post :destroy, :ids => [1, 3], :todo => 'destroy'
1204 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1204 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1205 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1205 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1206 assert_nil TimeEntry.find_by_id([1, 2])
1206 assert_nil TimeEntry.find_by_id([1, 2])
1207 end
1207 end
1208
1208
1209 def test_destroy_issues_and_assign_time_entries_to_project
1209 def test_destroy_issues_and_assign_time_entries_to_project
1210 @request.session[:user_id] = 2
1210 @request.session[:user_id] = 2
1211 post :destroy, :ids => [1, 3], :todo => 'nullify'
1211 post :destroy, :ids => [1, 3], :todo => 'nullify'
1212 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1212 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1213 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1213 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1214 assert_nil TimeEntry.find(1).issue_id
1214 assert_nil TimeEntry.find(1).issue_id
1215 assert_nil TimeEntry.find(2).issue_id
1215 assert_nil TimeEntry.find(2).issue_id
1216 end
1216 end
1217
1217
1218 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1218 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1219 @request.session[:user_id] = 2
1219 @request.session[:user_id] = 2
1220 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1220 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1221 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1221 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1222 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1222 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1223 assert_equal 2, TimeEntry.find(1).issue_id
1223 assert_equal 2, TimeEntry.find(1).issue_id
1224 assert_equal 2, TimeEntry.find(2).issue_id
1224 assert_equal 2, TimeEntry.find(2).issue_id
1225 end
1225 end
1226
1226
1227 def test_default_search_scope
1227 def test_default_search_scope
1228 get :index
1228 get :index
1229 assert_tag :div, :attributes => {:id => 'quick-search'},
1229 assert_tag :div, :attributes => {:id => 'quick-search'},
1230 :child => {:tag => 'form',
1230 :child => {:tag => 'form',
1231 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1231 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1232 end
1232 end
1233 end
1233 end
General Comments 0
You need to be logged in to leave comments. Login now