##// END OF EJS Templates
Added ability to delete issues from different projects through contextual menu (#5332)...
Jean-Baptiste Barth -
r4122:b255b7760ac6
parent child
Show More
@@ -1,415 +1,415
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'uri'
18 require 'uri'
19 require 'cgi'
19 require 'cgi'
20
20
21 class ApplicationController < ActionController::Base
21 class ApplicationController < ActionController::Base
22 include Redmine::I18n
22 include Redmine::I18n
23
23
24 layout 'base'
24 layout 'base'
25 exempt_from_layout 'builder'
25 exempt_from_layout 'builder'
26
26
27 # Remove broken cookie after upgrade from 0.8.x (#4292)
27 # Remove broken cookie after upgrade from 0.8.x (#4292)
28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
29 # TODO: remove it when Rails is fixed
29 # TODO: remove it when Rails is fixed
30 before_filter :delete_broken_cookies
30 before_filter :delete_broken_cookies
31 def delete_broken_cookies
31 def delete_broken_cookies
32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
33 cookies.delete '_redmine_session'
33 cookies.delete '_redmine_session'
34 redirect_to home_path
34 redirect_to home_path
35 return false
35 return false
36 end
36 end
37 end
37 end
38
38
39 before_filter :user_setup, :check_if_login_required, :set_localization
39 before_filter :user_setup, :check_if_login_required, :set_localization
40 filter_parameter_logging :password
40 filter_parameter_logging :password
41 protect_from_forgery
41 protect_from_forgery
42
42
43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
44
44
45 include Redmine::Search::Controller
45 include Redmine::Search::Controller
46 include Redmine::MenuManager::MenuController
46 include Redmine::MenuManager::MenuController
47 helper Redmine::MenuManager::MenuHelper
47 helper Redmine::MenuManager::MenuHelper
48
48
49 Redmine::Scm::Base.all.each do |scm|
49 Redmine::Scm::Base.all.each do |scm|
50 require_dependency "repository/#{scm.underscore}"
50 require_dependency "repository/#{scm.underscore}"
51 end
51 end
52
52
53 def user_setup
53 def user_setup
54 # Check the settings cache for each request
54 # Check the settings cache for each request
55 Setting.check_cache
55 Setting.check_cache
56 # Find the current user
56 # Find the current user
57 User.current = find_current_user
57 User.current = find_current_user
58 end
58 end
59
59
60 # Returns the current user or nil if no user is logged in
60 # Returns the current user or nil if no user is logged in
61 # and starts a session if needed
61 # and starts a session if needed
62 def find_current_user
62 def find_current_user
63 if session[:user_id]
63 if session[:user_id]
64 # existing session
64 # existing session
65 (User.active.find(session[:user_id]) rescue nil)
65 (User.active.find(session[:user_id]) rescue nil)
66 elsif cookies[:autologin] && Setting.autologin?
66 elsif cookies[:autologin] && Setting.autologin?
67 # auto-login feature starts a new session
67 # auto-login feature starts a new session
68 user = User.try_to_autologin(cookies[:autologin])
68 user = User.try_to_autologin(cookies[:autologin])
69 session[:user_id] = user.id if user
69 session[:user_id] = user.id if user
70 user
70 user
71 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
71 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
72 # RSS key authentication does not start a session
72 # RSS key authentication does not start a session
73 User.find_by_rss_key(params[:key])
73 User.find_by_rss_key(params[:key])
74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
75 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
75 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
76 # Use API key
76 # Use API key
77 User.find_by_api_key(params[:key])
77 User.find_by_api_key(params[:key])
78 else
78 else
79 # HTTP Basic, either username/password or API key/random
79 # HTTP Basic, either username/password or API key/random
80 authenticate_with_http_basic do |username, password|
80 authenticate_with_http_basic do |username, password|
81 User.try_to_login(username, password) || User.find_by_api_key(username)
81 User.try_to_login(username, password) || User.find_by_api_key(username)
82 end
82 end
83 end
83 end
84 end
84 end
85 end
85 end
86
86
87 # Sets the logged in user
87 # Sets the logged in user
88 def logged_user=(user)
88 def logged_user=(user)
89 reset_session
89 reset_session
90 if user && user.is_a?(User)
90 if user && user.is_a?(User)
91 User.current = user
91 User.current = user
92 session[:user_id] = user.id
92 session[:user_id] = user.id
93 else
93 else
94 User.current = User.anonymous
94 User.current = User.anonymous
95 end
95 end
96 end
96 end
97
97
98 # check if login is globally required to access the application
98 # check if login is globally required to access the application
99 def check_if_login_required
99 def check_if_login_required
100 # no check needed if user is already logged in
100 # no check needed if user is already logged in
101 return true if User.current.logged?
101 return true if User.current.logged?
102 require_login if Setting.login_required?
102 require_login if Setting.login_required?
103 end
103 end
104
104
105 def set_localization
105 def set_localization
106 lang = nil
106 lang = nil
107 if User.current.logged?
107 if User.current.logged?
108 lang = find_language(User.current.language)
108 lang = find_language(User.current.language)
109 end
109 end
110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
112 if !accept_lang.blank?
112 if !accept_lang.blank?
113 accept_lang = accept_lang.downcase
113 accept_lang = accept_lang.downcase
114 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
114 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
115 end
115 end
116 end
116 end
117 lang ||= Setting.default_language
117 lang ||= Setting.default_language
118 set_language_if_valid(lang)
118 set_language_if_valid(lang)
119 end
119 end
120
120
121 def require_login
121 def require_login
122 if !User.current.logged?
122 if !User.current.logged?
123 # Extract only the basic url parameters on non-GET requests
123 # Extract only the basic url parameters on non-GET requests
124 if request.get?
124 if request.get?
125 url = url_for(params)
125 url = url_for(params)
126 else
126 else
127 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
127 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
128 end
128 end
129 respond_to do |format|
129 respond_to do |format|
130 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
130 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
131 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
131 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
132 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
132 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
133 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
133 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
134 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
134 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
135 end
135 end
136 return false
136 return false
137 end
137 end
138 true
138 true
139 end
139 end
140
140
141 def require_admin
141 def require_admin
142 return unless require_login
142 return unless require_login
143 if !User.current.admin?
143 if !User.current.admin?
144 render_403
144 render_403
145 return false
145 return false
146 end
146 end
147 true
147 true
148 end
148 end
149
149
150 def deny_access
150 def deny_access
151 User.current.logged? ? render_403 : require_login
151 User.current.logged? ? render_403 : require_login
152 end
152 end
153
153
154 # Authorize the user for the requested action
154 # Authorize the user for the requested action
155 def authorize(ctrl = params[:controller], action = params[:action], global = false)
155 def authorize(ctrl = params[:controller], action = params[:action], global = false)
156 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project, :global => global)
156 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
157 allowed ? true : deny_access
157 allowed ? true : deny_access
158 end
158 end
159
159
160 # Authorize the user for the requested action outside a project
160 # Authorize the user for the requested action outside a project
161 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
161 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
162 authorize(ctrl, action, global)
162 authorize(ctrl, action, global)
163 end
163 end
164
164
165 # Find project of id params[:id]
165 # Find project of id params[:id]
166 def find_project
166 def find_project
167 @project = Project.find(params[:id])
167 @project = Project.find(params[:id])
168 rescue ActiveRecord::RecordNotFound
168 rescue ActiveRecord::RecordNotFound
169 render_404
169 render_404
170 end
170 end
171
171
172 # Find project of id params[:project_id]
172 # Find project of id params[:project_id]
173 def find_project_by_project_id
173 def find_project_by_project_id
174 @project = Project.find(params[:project_id])
174 @project = Project.find(params[:project_id])
175 rescue ActiveRecord::RecordNotFound
175 rescue ActiveRecord::RecordNotFound
176 render_404
176 render_404
177 end
177 end
178
178
179 # Find a project based on params[:project_id]
179 # Find a project based on params[:project_id]
180 # TODO: some subclasses override this, see about merging their logic
180 # TODO: some subclasses override this, see about merging their logic
181 def find_optional_project
181 def find_optional_project
182 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
182 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
183 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
183 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
184 allowed ? true : deny_access
184 allowed ? true : deny_access
185 rescue ActiveRecord::RecordNotFound
185 rescue ActiveRecord::RecordNotFound
186 render_404
186 render_404
187 end
187 end
188
188
189 # Finds and sets @project based on @object.project
189 # Finds and sets @project based on @object.project
190 def find_project_from_association
190 def find_project_from_association
191 render_404 unless @object.present?
191 render_404 unless @object.present?
192
192
193 @project = @object.project
193 @project = @object.project
194 rescue ActiveRecord::RecordNotFound
194 rescue ActiveRecord::RecordNotFound
195 render_404
195 render_404
196 end
196 end
197
197
198 def find_model_object
198 def find_model_object
199 model = self.class.read_inheritable_attribute('model_object')
199 model = self.class.read_inheritable_attribute('model_object')
200 if model
200 if model
201 @object = model.find(params[:id])
201 @object = model.find(params[:id])
202 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
202 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
203 end
203 end
204 rescue ActiveRecord::RecordNotFound
204 rescue ActiveRecord::RecordNotFound
205 render_404
205 render_404
206 end
206 end
207
207
208 def self.model_object(model)
208 def self.model_object(model)
209 write_inheritable_attribute('model_object', model)
209 write_inheritable_attribute('model_object', model)
210 end
210 end
211
211
212 # Filter for bulk issue operations
212 # Filter for bulk issue operations
213 def find_issues
213 def find_issues
214 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
214 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
215 raise ActiveRecord::RecordNotFound if @issues.empty?
215 raise ActiveRecord::RecordNotFound if @issues.empty?
216 @projects = @issues.collect(&:project).compact.uniq
216 @projects = @issues.collect(&:project).compact.uniq
217 @project = @projects.first if @projects.size == 1
217 @project = @projects.first if @projects.size == 1
218 rescue ActiveRecord::RecordNotFound
218 rescue ActiveRecord::RecordNotFound
219 render_404
219 render_404
220 end
220 end
221
221
222 # Check if project is unique before bulk operations
222 # Check if project is unique before bulk operations
223 def check_project_uniqueness
223 def check_project_uniqueness
224 unless @project
224 unless @project
225 # TODO: let users bulk edit/move/destroy issues from different projects
225 # TODO: let users bulk edit/move/destroy issues from different projects
226 render_error 'Can not bulk edit/move/destroy issues from different projects'
226 render_error 'Can not bulk edit/move/destroy issues from different projects'
227 return false
227 return false
228 end
228 end
229 end
229 end
230
230
231 # make sure that the user is a member of the project (or admin) if project is private
231 # make sure that the user is a member of the project (or admin) if project is private
232 # used as a before_filter for actions that do not require any particular permission on the project
232 # used as a before_filter for actions that do not require any particular permission on the project
233 def check_project_privacy
233 def check_project_privacy
234 if @project && @project.active?
234 if @project && @project.active?
235 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
235 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
236 true
236 true
237 else
237 else
238 User.current.logged? ? render_403 : require_login
238 User.current.logged? ? render_403 : require_login
239 end
239 end
240 else
240 else
241 @project = nil
241 @project = nil
242 render_404
242 render_404
243 false
243 false
244 end
244 end
245 end
245 end
246
246
247 def back_url
247 def back_url
248 params[:back_url] || request.env['HTTP_REFERER']
248 params[:back_url] || request.env['HTTP_REFERER']
249 end
249 end
250
250
251 def redirect_back_or_default(default)
251 def redirect_back_or_default(default)
252 back_url = CGI.unescape(params[:back_url].to_s)
252 back_url = CGI.unescape(params[:back_url].to_s)
253 if !back_url.blank?
253 if !back_url.blank?
254 begin
254 begin
255 uri = URI.parse(back_url)
255 uri = URI.parse(back_url)
256 # do not redirect user to another host or to the login or register page
256 # do not redirect user to another host or to the login or register page
257 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
257 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
258 redirect_to(back_url)
258 redirect_to(back_url)
259 return
259 return
260 end
260 end
261 rescue URI::InvalidURIError
261 rescue URI::InvalidURIError
262 # redirect to default
262 # redirect to default
263 end
263 end
264 end
264 end
265 redirect_to default
265 redirect_to default
266 end
266 end
267
267
268 def render_403
268 def render_403
269 @project = nil
269 @project = nil
270 respond_to do |format|
270 respond_to do |format|
271 format.html { render :template => "common/403", :layout => use_layout, :status => 403 }
271 format.html { render :template => "common/403", :layout => use_layout, :status => 403 }
272 format.atom { head 403 }
272 format.atom { head 403 }
273 format.xml { head 403 }
273 format.xml { head 403 }
274 format.js { head 403 }
274 format.js { head 403 }
275 format.json { head 403 }
275 format.json { head 403 }
276 end
276 end
277 return false
277 return false
278 end
278 end
279
279
280 def render_404
280 def render_404
281 respond_to do |format|
281 respond_to do |format|
282 format.html { render :template => "common/404", :layout => use_layout, :status => 404 }
282 format.html { render :template => "common/404", :layout => use_layout, :status => 404 }
283 format.atom { head 404 }
283 format.atom { head 404 }
284 format.xml { head 404 }
284 format.xml { head 404 }
285 format.js { head 404 }
285 format.js { head 404 }
286 format.json { head 404 }
286 format.json { head 404 }
287 end
287 end
288 return false
288 return false
289 end
289 end
290
290
291 def render_error(msg)
291 def render_error(msg)
292 respond_to do |format|
292 respond_to do |format|
293 format.html {
293 format.html {
294 flash.now[:error] = msg
294 flash.now[:error] = msg
295 render :text => '', :layout => use_layout, :status => 500
295 render :text => '', :layout => use_layout, :status => 500
296 }
296 }
297 format.atom { head 500 }
297 format.atom { head 500 }
298 format.xml { head 500 }
298 format.xml { head 500 }
299 format.js { head 500 }
299 format.js { head 500 }
300 format.json { head 500 }
300 format.json { head 500 }
301 end
301 end
302 end
302 end
303
303
304 # Picks which layout to use based on the request
304 # Picks which layout to use based on the request
305 #
305 #
306 # @return [boolean, string] name of the layout to use or false for no layout
306 # @return [boolean, string] name of the layout to use or false for no layout
307 def use_layout
307 def use_layout
308 request.xhr? ? false : 'base'
308 request.xhr? ? false : 'base'
309 end
309 end
310
310
311 def invalid_authenticity_token
311 def invalid_authenticity_token
312 if api_request?
312 if api_request?
313 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
313 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
314 end
314 end
315 render_error "Invalid form authenticity token."
315 render_error "Invalid form authenticity token."
316 end
316 end
317
317
318 def render_feed(items, options={})
318 def render_feed(items, options={})
319 @items = items || []
319 @items = items || []
320 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
320 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
321 @items = @items.slice(0, Setting.feeds_limit.to_i)
321 @items = @items.slice(0, Setting.feeds_limit.to_i)
322 @title = options[:title] || Setting.app_title
322 @title = options[:title] || Setting.app_title
323 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
323 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
324 end
324 end
325
325
326 def self.accept_key_auth(*actions)
326 def self.accept_key_auth(*actions)
327 actions = actions.flatten.map(&:to_s)
327 actions = actions.flatten.map(&:to_s)
328 write_inheritable_attribute('accept_key_auth_actions', actions)
328 write_inheritable_attribute('accept_key_auth_actions', actions)
329 end
329 end
330
330
331 def accept_key_auth_actions
331 def accept_key_auth_actions
332 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
332 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
333 end
333 end
334
334
335 # Returns the number of objects that should be displayed
335 # Returns the number of objects that should be displayed
336 # on the paginated list
336 # on the paginated list
337 def per_page_option
337 def per_page_option
338 per_page = nil
338 per_page = nil
339 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
339 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
340 per_page = params[:per_page].to_s.to_i
340 per_page = params[:per_page].to_s.to_i
341 session[:per_page] = per_page
341 session[:per_page] = per_page
342 elsif session[:per_page]
342 elsif session[:per_page]
343 per_page = session[:per_page]
343 per_page = session[:per_page]
344 else
344 else
345 per_page = Setting.per_page_options_array.first || 25
345 per_page = Setting.per_page_options_array.first || 25
346 end
346 end
347 per_page
347 per_page
348 end
348 end
349
349
350 # qvalues http header parser
350 # qvalues http header parser
351 # code taken from webrick
351 # code taken from webrick
352 def parse_qvalues(value)
352 def parse_qvalues(value)
353 tmp = []
353 tmp = []
354 if value
354 if value
355 parts = value.split(/,\s*/)
355 parts = value.split(/,\s*/)
356 parts.each {|part|
356 parts.each {|part|
357 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
357 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
358 val = m[1]
358 val = m[1]
359 q = (m[2] or 1).to_f
359 q = (m[2] or 1).to_f
360 tmp.push([val, q])
360 tmp.push([val, q])
361 end
361 end
362 }
362 }
363 tmp = tmp.sort_by{|val, q| -q}
363 tmp = tmp.sort_by{|val, q| -q}
364 tmp.collect!{|val, q| val}
364 tmp.collect!{|val, q| val}
365 end
365 end
366 return tmp
366 return tmp
367 rescue
367 rescue
368 nil
368 nil
369 end
369 end
370
370
371 # Returns a string that can be used as filename value in Content-Disposition header
371 # Returns a string that can be used as filename value in Content-Disposition header
372 def filename_for_content_disposition(name)
372 def filename_for_content_disposition(name)
373 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
373 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
374 end
374 end
375
375
376 def api_request?
376 def api_request?
377 %w(xml json).include? params[:format]
377 %w(xml json).include? params[:format]
378 end
378 end
379
379
380 # Renders a warning flash if obj has unsaved attachments
380 # Renders a warning flash if obj has unsaved attachments
381 def render_attachment_warning_if_needed(obj)
381 def render_attachment_warning_if_needed(obj)
382 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
382 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
383 end
383 end
384
384
385 # Sets the `flash` notice or error based the number of issues that did not save
385 # Sets the `flash` notice or error based the number of issues that did not save
386 #
386 #
387 # @param [Array, Issue] issues all of the saved and unsaved Issues
387 # @param [Array, Issue] issues all of the saved and unsaved Issues
388 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
388 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
389 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
389 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
390 if unsaved_issue_ids.empty?
390 if unsaved_issue_ids.empty?
391 flash[:notice] = l(:notice_successful_update) unless issues.empty?
391 flash[:notice] = l(:notice_successful_update) unless issues.empty?
392 else
392 else
393 flash[:error] = l(:notice_failed_to_save_issues,
393 flash[:error] = l(:notice_failed_to_save_issues,
394 :count => unsaved_issue_ids.size,
394 :count => unsaved_issue_ids.size,
395 :total => issues.size,
395 :total => issues.size,
396 :ids => '#' + unsaved_issue_ids.join(', #'))
396 :ids => '#' + unsaved_issue_ids.join(', #'))
397 end
397 end
398 end
398 end
399
399
400 # Rescues an invalid query statement. Just in case...
400 # Rescues an invalid query statement. Just in case...
401 def query_statement_invalid(exception)
401 def query_statement_invalid(exception)
402 logger.error "Query::StatementInvalid: #{exception.message}" if logger
402 logger.error "Query::StatementInvalid: #{exception.message}" if logger
403 session.delete(:query)
403 session.delete(:query)
404 sort_clear if respond_to?(:sort_clear)
404 sort_clear if respond_to?(:sort_clear)
405 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
405 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
406 end
406 end
407
407
408 # Converts the errors on an ActiveRecord object into a common JSON format
408 # Converts the errors on an ActiveRecord object into a common JSON format
409 def object_errors_to_json(object)
409 def object_errors_to_json(object)
410 object.errors.collect do |attribute, error|
410 object.errors.collect do |attribute, error|
411 { attribute => error }
411 { attribute => error }
412 end.to_json
412 end.to_json
413 end
413 end
414
414
415 end
415 end
@@ -1,39 +1,39
1 class ContextMenusController < ApplicationController
1 class ContextMenusController < ApplicationController
2 helper :watchers
2 helper :watchers
3
3
4 def issues
4 def issues
5 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
5 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
6 if (@issues.size == 1)
6 if (@issues.size == 1)
7 @issue = @issues.first
7 @issue = @issues.first
8 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
8 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
9 else
9 else
10 @allowed_statuses = @issues.map do |i|
10 @allowed_statuses = @issues.map do |i|
11 i.new_statuses_allowed_to(User.current)
11 i.new_statuses_allowed_to(User.current)
12 end.inject do |memo,s|
12 end.inject do |memo,s|
13 memo & s
13 memo & s
14 end
14 end
15 end
15 end
16 @projects = @issues.collect(&:project).compact.uniq
16 @projects = @issues.collect(&:project).compact.uniq
17 @project = @projects.first if @projects.size == 1
17 @project = @projects.first if @projects.size == 1
18
18
19 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
19 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
20 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
20 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
21 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
21 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
22 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
22 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
23 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
23 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
24 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
24 :delete => User.current.allowed_to?(:delete_issues, @projects)
25 }
25 }
26 if @project
26 if @project
27 @assignables = @project.assignable_users
27 @assignables = @project.assignable_users
28 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
28 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
29 @trackers = @project.trackers
29 @trackers = @project.trackers
30 end
30 end
31
31
32 @priorities = IssuePriority.all.reverse
32 @priorities = IssuePriority.all.reverse
33 @statuses = IssueStatus.find(:all, :order => 'position')
33 @statuses = IssueStatus.find(:all, :order => 'position')
34 @back = back_url
34 @back = back_url
35
35
36 render :layout => false
36 render :layout => false
37 end
37 end
38
38
39 end
39 end
@@ -1,330 +1,330
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, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
24 before_filter :check_project_uniqueness, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
24 before_filter :check_project_uniqueness, :only => [:bulk_edit, :bulk_update, :move, :perform_move]
25 before_filter :find_project, :only => [:new, :create]
25 before_filter :find_project, :only => [:new, :create]
26 before_filter :authorize, :except => [:index]
26 before_filter :authorize, :except => [:index]
27 before_filter :find_optional_project, :only => [:index]
27 before_filter :find_optional_project, :only => [:index]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
30 accept_key_auth :index, :show
30 accept_key_auth :index, :show
31
31
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33
33
34 helper :journals
34 helper :journals
35 helper :projects
35 helper :projects
36 include ProjectsHelper
36 include ProjectsHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :issue_relations
39 helper :issue_relations
40 include IssueRelationsHelper
40 include IssueRelationsHelper
41 helper :watchers
41 helper :watchers
42 include WatchersHelper
42 include WatchersHelper
43 helper :attachments
43 helper :attachments
44 include AttachmentsHelper
44 include AttachmentsHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :sort
47 helper :sort
48 include SortHelper
48 include SortHelper
49 include IssuesHelper
49 include IssuesHelper
50 helper :timelog
50 helper :timelog
51 helper :gantt
51 helper :gantt
52 include Redmine::Export::PDF
52 include Redmine::Export::PDF
53
53
54 verify :method => [:post, :delete],
54 verify :method => [:post, :delete],
55 :only => :destroy,
55 :only => :destroy,
56 :render => { :nothing => true, :status => :method_not_allowed }
56 :render => { :nothing => true, :status => :method_not_allowed }
57
57
58 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
58 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
60 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
60 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
61
61
62 def index
62 def index
63 retrieve_query
63 retrieve_query
64 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
64 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
65 sort_update(@query.sortable_columns)
65 sort_update(@query.sortable_columns)
66
66
67 if @query.valid?
67 if @query.valid?
68 limit = case params[:format]
68 limit = case params[:format]
69 when 'csv', 'pdf'
69 when 'csv', 'pdf'
70 Setting.issues_export_limit.to_i
70 Setting.issues_export_limit.to_i
71 when 'atom'
71 when 'atom'
72 Setting.feeds_limit.to_i
72 Setting.feeds_limit.to_i
73 else
73 else
74 per_page_option
74 per_page_option
75 end
75 end
76
76
77 @issue_count = @query.issue_count
77 @issue_count = @query.issue_count
78 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
78 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
80 :order => sort_clause,
80 :order => sort_clause,
81 :offset => @issue_pages.current.offset,
81 :offset => @issue_pages.current.offset,
82 :limit => limit)
82 :limit => limit)
83 @issue_count_by_group = @query.issue_count_by_group
83 @issue_count_by_group = @query.issue_count_by_group
84
84
85 respond_to do |format|
85 respond_to do |format|
86 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
86 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
87 format.xml { render :layout => false }
87 format.xml { render :layout => false }
88 format.json { render :text => @issues.to_json, :layout => false }
88 format.json { render :text => @issues.to_json, :layout => false }
89 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
89 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
90 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
90 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
91 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
91 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
92 end
92 end
93 else
93 else
94 # Send html if the query is not valid
94 # Send html if the query is not valid
95 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
95 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
96 end
96 end
97 rescue ActiveRecord::RecordNotFound
97 rescue ActiveRecord::RecordNotFound
98 render_404
98 render_404
99 end
99 end
100
100
101 def show
101 def show
102 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
102 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
103 @journals.each_with_index {|j,i| j.indice = i+1}
103 @journals.each_with_index {|j,i| j.indice = i+1}
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105 @changesets = @issue.changesets.visible.all
105 @changesets = @issue.changesets.visible.all
106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
107 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
107 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
108 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
108 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
109 @priorities = IssuePriority.all
109 @priorities = IssuePriority.all
110 @time_entry = TimeEntry.new
110 @time_entry = TimeEntry.new
111 respond_to do |format|
111 respond_to do |format|
112 format.html { render :template => 'issues/show.rhtml' }
112 format.html { render :template => 'issues/show.rhtml' }
113 format.xml { render :layout => false }
113 format.xml { render :layout => false }
114 format.json { render :text => @issue.to_json, :layout => false }
114 format.json { render :text => @issue.to_json, :layout => false }
115 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
115 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
116 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
116 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
117 end
117 end
118 end
118 end
119
119
120 # Add a new issue
120 # Add a new issue
121 # The new issue will be created from an existing one if copy_from parameter is given
121 # The new issue will be created from an existing one if copy_from parameter is given
122 def new
122 def new
123 respond_to do |format|
123 respond_to do |format|
124 format.html { render :action => 'new', :layout => !request.xhr? }
124 format.html { render :action => 'new', :layout => !request.xhr? }
125 format.js { render :partial => 'attributes' }
125 format.js { render :partial => 'attributes' }
126 end
126 end
127 end
127 end
128
128
129 def create
129 def create
130 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
130 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
131 if @issue.save
131 if @issue.save
132 attachments = Attachment.attach_files(@issue, params[:attachments])
132 attachments = Attachment.attach_files(@issue, params[:attachments])
133 render_attachment_warning_if_needed(@issue)
133 render_attachment_warning_if_needed(@issue)
134 flash[:notice] = l(:notice_successful_create)
134 flash[:notice] = l(:notice_successful_create)
135 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
135 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
136 respond_to do |format|
136 respond_to do |format|
137 format.html {
137 format.html {
138 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
138 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
139 { :action => 'show', :id => @issue })
139 { :action => 'show', :id => @issue })
140 }
140 }
141 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
141 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
142 format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false }
142 format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false }
143 end
143 end
144 return
144 return
145 else
145 else
146 respond_to do |format|
146 respond_to do |format|
147 format.html { render :action => 'new' }
147 format.html { render :action => 'new' }
148 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
148 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
149 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
149 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
150 end
150 end
151 end
151 end
152 end
152 end
153
153
154 # Attributes that can be updated on workflow transition (without :edit permission)
154 # Attributes that can be updated on workflow transition (without :edit permission)
155 # TODO: make it configurable (at least per role)
155 # TODO: make it configurable (at least per role)
156 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
156 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
157
157
158 def edit
158 def edit
159 update_issue_from_params
159 update_issue_from_params
160
160
161 @journal = @issue.current_journal
161 @journal = @issue.current_journal
162
162
163 respond_to do |format|
163 respond_to do |format|
164 format.html { }
164 format.html { }
165 format.xml { }
165 format.xml { }
166 end
166 end
167 end
167 end
168
168
169 def update
169 def update
170 update_issue_from_params
170 update_issue_from_params
171
171
172 if @issue.save_issue_with_child_records(params, @time_entry)
172 if @issue.save_issue_with_child_records(params, @time_entry)
173 render_attachment_warning_if_needed(@issue)
173 render_attachment_warning_if_needed(@issue)
174 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
174 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
175
175
176 respond_to do |format|
176 respond_to do |format|
177 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
177 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
178 format.xml { head :ok }
178 format.xml { head :ok }
179 format.json { head :ok }
179 format.json { head :ok }
180 end
180 end
181 else
181 else
182 render_attachment_warning_if_needed(@issue)
182 render_attachment_warning_if_needed(@issue)
183 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
183 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
184 @journal = @issue.current_journal
184 @journal = @issue.current_journal
185
185
186 respond_to do |format|
186 respond_to do |format|
187 format.html { render :action => 'edit' }
187 format.html { render :action => 'edit' }
188 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
188 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
189 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
189 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
190 end
190 end
191 end
191 end
192 end
192 end
193
193
194 # Bulk edit a set of issues
194 # Bulk edit a set of issues
195 def bulk_edit
195 def bulk_edit
196 @issues.sort!
196 @issues.sort!
197 @available_statuses = Workflow.available_statuses(@project)
197 @available_statuses = Workflow.available_statuses(@project)
198 @custom_fields = @project.all_issue_custom_fields
198 @custom_fields = @project.all_issue_custom_fields
199 end
199 end
200
200
201 def bulk_update
201 def bulk_update
202 @issues.sort!
202 @issues.sort!
203 attributes = parse_params_for_bulk_issue_attributes(params)
203 attributes = parse_params_for_bulk_issue_attributes(params)
204
204
205 unsaved_issue_ids = []
205 unsaved_issue_ids = []
206 @issues.each do |issue|
206 @issues.each do |issue|
207 issue.reload
207 issue.reload
208 journal = issue.init_journal(User.current, params[:notes])
208 journal = issue.init_journal(User.current, params[:notes])
209 issue.safe_attributes = attributes
209 issue.safe_attributes = attributes
210 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
210 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
211 unless issue.save
211 unless issue.save
212 # Keep unsaved issue ids to display them in flash error
212 # Keep unsaved issue ids to display them in flash error
213 unsaved_issue_ids << issue.id
213 unsaved_issue_ids << issue.id
214 end
214 end
215 end
215 end
216 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
216 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
217 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
217 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
218 end
218 end
219
219
220 def destroy
220 def destroy
221 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
221 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
222 if @hours > 0
222 if @hours > 0
223 case params[:todo]
223 case params[:todo]
224 when 'destroy'
224 when 'destroy'
225 # nothing to do
225 # nothing to do
226 when 'nullify'
226 when 'nullify'
227 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
227 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
228 when 'reassign'
228 when 'reassign'
229 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
229 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
230 if reassign_to.nil?
230 if reassign_to.nil?
231 flash.now[:error] = l(:error_issue_not_found_in_project)
231 flash.now[:error] = l(:error_issue_not_found_in_project)
232 return
232 return
233 else
233 else
234 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
234 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
235 end
235 end
236 else
236 else
237 unless params[:format] == 'xml' || params[:format] == 'json'
237 unless params[:format] == 'xml' || params[:format] == 'json'
238 # display the destroy form if it's a user request
238 # display the destroy form if it's a user request
239 return
239 return
240 end
240 end
241 end
241 end
242 end
242 end
243 @issues.each(&:destroy)
243 @issues.each(&:destroy)
244 respond_to do |format|
244 respond_to do |format|
245 format.html { redirect_to :action => 'index', :project_id => @project }
245 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
246 format.xml { head :ok }
246 format.xml { head :ok }
247 format.json { head :ok }
247 format.json { head :ok }
248 end
248 end
249 end
249 end
250
250
251 private
251 private
252 def find_issue
252 def find_issue
253 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
253 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
254 @project = @issue.project
254 @project = @issue.project
255 rescue ActiveRecord::RecordNotFound
255 rescue ActiveRecord::RecordNotFound
256 render_404
256 render_404
257 end
257 end
258
258
259 def find_project
259 def find_project
260 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
260 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
261 @project = Project.find(project_id)
261 @project = Project.find(project_id)
262 rescue ActiveRecord::RecordNotFound
262 rescue ActiveRecord::RecordNotFound
263 render_404
263 render_404
264 end
264 end
265
265
266 # Used by #edit and #update to set some common instance variables
266 # Used by #edit and #update to set some common instance variables
267 # from the params
267 # from the params
268 # TODO: Refactor, not everything in here is needed by #edit
268 # TODO: Refactor, not everything in here is needed by #edit
269 def update_issue_from_params
269 def update_issue_from_params
270 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
270 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
271 @priorities = IssuePriority.all
271 @priorities = IssuePriority.all
272 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
272 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
273 @time_entry = TimeEntry.new
273 @time_entry = TimeEntry.new
274
274
275 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
275 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
276 @issue.init_journal(User.current, @notes)
276 @issue.init_journal(User.current, @notes)
277 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
277 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
278 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
278 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
279 attrs = params[:issue].dup
279 attrs = params[:issue].dup
280 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
280 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
281 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
281 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
282 @issue.safe_attributes = attrs
282 @issue.safe_attributes = attrs
283 end
283 end
284
284
285 end
285 end
286
286
287 # TODO: Refactor, lots of extra code in here
287 # TODO: Refactor, lots of extra code in here
288 # TODO: Changing tracker on an existing issue should not trigger this
288 # TODO: Changing tracker on an existing issue should not trigger this
289 def build_new_issue_from_params
289 def build_new_issue_from_params
290 if params[:id].blank?
290 if params[:id].blank?
291 @issue = Issue.new
291 @issue = Issue.new
292 @issue.copy_from(params[:copy_from]) if params[:copy_from]
292 @issue.copy_from(params[:copy_from]) if params[:copy_from]
293 @issue.project = @project
293 @issue.project = @project
294 else
294 else
295 @issue = @project.issues.visible.find(params[:id])
295 @issue = @project.issues.visible.find(params[:id])
296 end
296 end
297
297
298 @issue.project = @project
298 @issue.project = @project
299 # Tracker must be set before custom field values
299 # Tracker must be set before custom field values
300 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
300 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
301 if @issue.tracker.nil?
301 if @issue.tracker.nil?
302 render_error l(:error_no_tracker_in_project)
302 render_error l(:error_no_tracker_in_project)
303 return false
303 return false
304 end
304 end
305 if params[:issue].is_a?(Hash)
305 if params[:issue].is_a?(Hash)
306 @issue.safe_attributes = params[:issue]
306 @issue.safe_attributes = params[:issue]
307 if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
307 if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
308 @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
308 @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
309 end
309 end
310 end
310 end
311 @issue.author = User.current
311 @issue.author = User.current
312 @issue.start_date ||= Date.today
312 @issue.start_date ||= Date.today
313 @priorities = IssuePriority.all
313 @priorities = IssuePriority.all
314 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
314 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
315 end
315 end
316
316
317 def check_for_default_issue_status
317 def check_for_default_issue_status
318 if IssueStatus.default.nil?
318 if IssueStatus.default.nil?
319 render_error l(:error_no_default_issue_status)
319 render_error l(:error_no_default_issue_status)
320 return false
320 return false
321 end
321 end
322 end
322 end
323
323
324 def parse_params_for_bulk_issue_attributes(params)
324 def parse_params_for_bulk_issue_attributes(params)
325 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
325 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
326 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
326 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
327 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
327 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
328 attributes
328 attributes
329 end
329 end
330 end
330 end
@@ -1,122 +1,122
1 <ul>
1 <ul>
2 <%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %>
2 <%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %>
3
3
4 <% if !@issue.nil? -%>
4 <% if !@issue.nil? -%>
5 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
5 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
6 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
6 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
7 <% else %>
7 <% else %>
8 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
8 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
9 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
9 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
10 <% end %>
10 <% end %>
11
11
12 <% unless @allowed_statuses.empty? %>
12 <% unless @allowed_statuses.empty? %>
13 <li class="folder">
13 <li class="folder">
14 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
14 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
15 <ul>
15 <ul>
16 <% @statuses.each do |s| -%>
16 <% @statuses.each do |s| -%>
17 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post,
17 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post,
18 :selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
18 :selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
19 <% end -%>
19 <% end -%>
20 </ul>
20 </ul>
21 </li>
21 </li>
22 <% end %>
22 <% end %>
23
23
24 <% unless @trackers.nil? %>
24 <% unless @trackers.nil? %>
25 <li class="folder">
25 <li class="folder">
26 <a href="#" class="submenu"><%= l(:field_tracker) %></a>
26 <a href="#" class="submenu"><%= l(:field_tracker) %></a>
27 <ul>
27 <ul>
28 <% @trackers.each do |t| -%>
28 <% @trackers.each do |t| -%>
29 <li><%= context_menu_link t.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'tracker_id' => t}, :back_url => @back}, :method => :post,
29 <li><%= context_menu_link t.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'tracker_id' => t}, :back_url => @back}, :method => :post,
30 :selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %></li>
30 :selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %></li>
31 <% end -%>
31 <% end -%>
32 </ul>
32 </ul>
33 </li>
33 </li>
34 <% end %>
34 <% end %>
35
35
36 <% if @projects.size == 1 %>
36 <% if @projects.size == 1 %>
37 <li class="folder">
37 <li class="folder">
38 <a href="#" class="submenu"><%= l(:field_priority) %></a>
38 <a href="#" class="submenu"><%= l(:field_priority) %></a>
39 <ul>
39 <ul>
40 <% @priorities.each do |p| -%>
40 <% @priorities.each do |p| -%>
41 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'priority_id' => p}, :back_url => @back}, :method => :post,
41 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'priority_id' => p}, :back_url => @back}, :method => :post,
42 :selected => (@issue && p == @issue.priority), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li>
42 :selected => (@issue && p == @issue.priority), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li>
43 <% end -%>
43 <% end -%>
44 </ul>
44 </ul>
45 </li>
45 </li>
46 <% end %>
46 <% end %>
47
47
48 <% unless @project.nil? || @project.shared_versions.open.empty? -%>
48 <% unless @project.nil? || @project.shared_versions.open.empty? -%>
49 <li class="folder">
49 <li class="folder">
50 <a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
50 <a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
51 <ul>
51 <ul>
52 <% @project.shared_versions.open.sort.each do |v| -%>
52 <% @project.shared_versions.open.sort.each do |v| -%>
53 <li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => v}, :back_url => @back}, :method => :post,
53 <li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => v}, :back_url => @back}, :method => :post,
54 :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %></li>
54 :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %></li>
55 <% end -%>
55 <% end -%>
56 <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => 'none'}, :back_url => @back}, :method => :post,
56 <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => 'none'}, :back_url => @back}, :method => :post,
57 :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %></li>
57 :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %></li>
58 </ul>
58 </ul>
59 </li>
59 </li>
60 <% end %>
60 <% end %>
61 <% unless @assignables.nil? || @assignables.empty? -%>
61 <% unless @assignables.nil? || @assignables.empty? -%>
62 <li class="folder">
62 <li class="folder">
63 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
63 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
64 <ul>
64 <ul>
65 <% @assignables.each do |u| -%>
65 <% @assignables.each do |u| -%>
66 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => u}, :back_url => @back}, :method => :post,
66 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => u}, :back_url => @back}, :method => :post,
67 :selected => (@issue && u == @issue.assigned_to), :disabled => !@can[:update] %></li>
67 :selected => (@issue && u == @issue.assigned_to), :disabled => !@can[:update] %></li>
68 <% end -%>
68 <% end -%>
69 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => 'none'}, :back_url => @back}, :method => :post,
69 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => 'none'}, :back_url => @back}, :method => :post,
70 :selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %></li>
70 :selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %></li>
71 </ul>
71 </ul>
72 </li>
72 </li>
73 <% end %>
73 <% end %>
74 <% unless @project.nil? || @project.issue_categories.empty? -%>
74 <% unless @project.nil? || @project.issue_categories.empty? -%>
75 <li class="folder">
75 <li class="folder">
76 <a href="#" class="submenu"><%= l(:field_category) %></a>
76 <a href="#" class="submenu"><%= l(:field_category) %></a>
77 <ul>
77 <ul>
78 <% @project.issue_categories.each do |u| -%>
78 <% @project.issue_categories.each do |u| -%>
79 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post,
79 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post,
80 :selected => (@issue && u == @issue.category), :disabled => !@can[:update] %></li>
80 :selected => (@issue && u == @issue.category), :disabled => !@can[:update] %></li>
81 <% end -%>
81 <% end -%>
82 <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => 'none'}, :back_url => @back}, :method => :post,
82 <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => 'none'}, :back_url => @back}, :method => :post,
83 :selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %></li>
83 :selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %></li>
84 </ul>
84 </ul>
85 </li>
85 </li>
86 <% end -%>
86 <% end -%>
87
87
88 <% if Issue.use_field_for_done_ratio? && @projects.size == 1 %>
88 <% if Issue.use_field_for_done_ratio? && @projects.size == 1 %>
89 <li class="folder">
89 <li class="folder">
90 <a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
90 <a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
91 <ul>
91 <ul>
92 <% (0..10).map{|x|x*10}.each do |p| -%>
92 <% (0..10).map{|x|x*10}.each do |p| -%>
93 <li><%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'done_ratio' => p}, :back_url => @back}, :method => :post,
93 <li><%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'done_ratio' => p}, :back_url => @back}, :method => :post,
94 :selected => (@issue && p == @issue.done_ratio), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li>
94 :selected => (@issue && p == @issue.done_ratio), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li>
95 <% end -%>
95 <% end -%>
96 </ul>
96 </ul>
97 </li>
97 </li>
98 <% end %>
98 <% end %>
99
99
100 <% if !@issue.nil? %>
100 <% if !@issue.nil? %>
101 <% if @can[:log_time] -%>
101 <% if @can[:log_time] -%>
102 <li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},
102 <li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},
103 :class => 'icon-time-add' %></li>
103 :class => 'icon-time-add' %></li>
104 <% end %>
104 <% end %>
105 <% if User.current.logged? %>
105 <% if User.current.logged? %>
106 <li><%= watcher_link(@issue, User.current) %></li>
106 <li><%= watcher_link(@issue, User.current) %></li>
107 <% end %>
107 <% end %>
108 <% end %>
108 <% end %>
109
109
110 <% if @issue.present? %>
110 <% if @issue.present? %>
111 <li><%= context_menu_link l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
111 <li><%= context_menu_link l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
112 :class => 'icon-duplicate', :disabled => !@can[:copy] %></li>
112 :class => 'icon-duplicate', :disabled => !@can[:copy] %></li>
113 <% end %>
113 <% end %>
114 <li><%= context_menu_link l(:button_copy), new_issue_move_path(:ids => @issues.collect(&:id), :copy_options => {:copy => 't'}),
114 <li><%= context_menu_link l(:button_copy), new_issue_move_path(:ids => @issues.collect(&:id), :copy_options => {:copy => 't'}),
115 :class => 'icon-copy', :disabled => !@can[:move] %></li>
115 :class => 'icon-copy', :disabled => !@can[:move] %></li>
116 <li><%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)),
116 <li><%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)),
117 :class => 'icon-move', :disabled => !@can[:move] %></li>
117 :class => 'icon-move', :disabled => !@can[:move] %></li>
118 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id)},
118 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id), :back_url => @back},
119 :method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %></li>
119 :method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %></li>
120
120
121 <%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %>
121 <%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %>
122 </ul>
122 </ul>
@@ -1,92 +1,93
1 require File.dirname(__FILE__) + '/../test_helper'
1 require File.dirname(__FILE__) + '/../test_helper'
2
2
3 class ContextMenusControllerTest < ActionController::TestCase
3 class ContextMenusControllerTest < ActionController::TestCase
4 fixtures :all
4 fixtures :all
5
5
6 def test_context_menu_one_issue
6 def test_context_menu_one_issue
7 @request.session[:user_id] = 2
7 @request.session[:user_id] = 2
8 get :issues, :ids => [1]
8 get :issues, :ids => [1]
9 assert_response :success
9 assert_response :success
10 assert_template 'context_menu'
10 assert_template 'context_menu'
11 assert_tag :tag => 'a', :content => 'Edit',
11 assert_tag :tag => 'a', :content => 'Edit',
12 :attributes => { :href => '/issues/1/edit',
12 :attributes => { :href => '/issues/1/edit',
13 :class => 'icon-edit' }
13 :class => 'icon-edit' }
14 assert_tag :tag => 'a', :content => 'Closed',
14 assert_tag :tag => 'a', :content => 'Closed',
15 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bstatus_id%5D=5',
15 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bstatus_id%5D=5',
16 :class => '' }
16 :class => '' }
17 assert_tag :tag => 'a', :content => 'Immediate',
17 assert_tag :tag => 'a', :content => 'Immediate',
18 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bpriority_id%5D=8',
18 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bpriority_id%5D=8',
19 :class => '' }
19 :class => '' }
20 # Versions
20 # Versions
21 assert_tag :tag => 'a', :content => '2.0',
21 assert_tag :tag => 'a', :content => '2.0',
22 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bfixed_version_id%5D=3',
22 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bfixed_version_id%5D=3',
23 :class => '' }
23 :class => '' }
24 assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0',
24 assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0',
25 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bfixed_version_id%5D=4',
25 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bfixed_version_id%5D=4',
26 :class => '' }
26 :class => '' }
27
27
28 assert_tag :tag => 'a', :content => 'Dave Lopper',
28 assert_tag :tag => 'a', :content => 'Dave Lopper',
29 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bassigned_to_id%5D=3',
29 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;issue%5Bassigned_to_id%5D=3',
30 :class => '' }
30 :class => '' }
31 assert_tag :tag => 'a', :content => 'Duplicate',
31 assert_tag :tag => 'a', :content => 'Duplicate',
32 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
32 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
33 :class => 'icon-duplicate' }
33 :class => 'icon-duplicate' }
34 assert_tag :tag => 'a', :content => 'Copy',
34 assert_tag :tag => 'a', :content => 'Copy',
35 :attributes => { :href => '/issues/move/new?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1',
35 :attributes => { :href => '/issues/move/new?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1',
36 :class => 'icon-copy' }
36 :class => 'icon-copy' }
37 assert_tag :tag => 'a', :content => 'Move',
37 assert_tag :tag => 'a', :content => 'Move',
38 :attributes => { :href => '/issues/move/new?ids%5B%5D=1',
38 :attributes => { :href => '/issues/move/new?ids%5B%5D=1',
39 :class => 'icon-move' }
39 :class => 'icon-move' }
40 assert_tag :tag => 'a', :content => 'Delete',
40 assert_tag :tag => 'a', :content => 'Delete',
41 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
41 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
42 :class => 'icon-del' }
42 :class => 'icon-del' }
43 end
43 end
44
44
45 def test_context_menu_one_issue_by_anonymous
45 def test_context_menu_one_issue_by_anonymous
46 get :issues, :ids => [1]
46 get :issues, :ids => [1]
47 assert_response :success
47 assert_response :success
48 assert_template 'context_menu'
48 assert_template 'context_menu'
49 assert_tag :tag => 'a', :content => 'Delete',
49 assert_tag :tag => 'a', :content => 'Delete',
50 :attributes => { :href => '#',
50 :attributes => { :href => '#',
51 :class => 'icon-del disabled' }
51 :class => 'icon-del disabled' }
52 end
52 end
53
53
54 def test_context_menu_multiple_issues_of_same_project
54 def test_context_menu_multiple_issues_of_same_project
55 @request.session[:user_id] = 2
55 @request.session[:user_id] = 2
56 get :issues, :ids => [1, 2]
56 get :issues, :ids => [1, 2]
57 assert_response :success
57 assert_response :success
58 assert_template 'context_menu'
58 assert_template 'context_menu'
59 assert_tag :tag => 'a', :content => 'Edit',
59 assert_tag :tag => 'a', :content => 'Edit',
60 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
60 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
61 :class => 'icon-edit' }
61 :class => 'icon-edit' }
62 assert_tag :tag => 'a', :content => 'Closed',
62 assert_tag :tag => 'a', :content => 'Closed',
63 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;issue%5Bstatus_id%5D=5',
63 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;issue%5Bstatus_id%5D=5',
64 :class => '' }
64 :class => '' }
65 assert_tag :tag => 'a', :content => 'Immediate',
65 assert_tag :tag => 'a', :content => 'Immediate',
66 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;issue%5Bpriority_id%5D=8',
66 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;issue%5Bpriority_id%5D=8',
67 :class => '' }
67 :class => '' }
68 assert_tag :tag => 'a', :content => 'Dave Lopper',
68 assert_tag :tag => 'a', :content => 'Dave Lopper',
69 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;issue%5Bassigned_to_id%5D=3',
69 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;issue%5Bassigned_to_id%5D=3',
70 :class => '' }
70 :class => '' }
71 assert_tag :tag => 'a', :content => 'Copy',
71 assert_tag :tag => 'a', :content => 'Copy',
72 :attributes => { :href => '/issues/move/new?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
72 :attributes => { :href => '/issues/move/new?copy_options%5Bcopy%5D=t&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
73 :class => 'icon-copy' }
73 :class => 'icon-copy' }
74 assert_tag :tag => 'a', :content => 'Move',
74 assert_tag :tag => 'a', :content => 'Move',
75 :attributes => { :href => '/issues/move/new?ids%5B%5D=1&amp;ids%5B%5D=2',
75 :attributes => { :href => '/issues/move/new?ids%5B%5D=1&amp;ids%5B%5D=2',
76 :class => 'icon-move' }
76 :class => 'icon-move' }
77 assert_tag :tag => 'a', :content => 'Delete',
77 assert_tag :tag => 'a', :content => 'Delete',
78 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
78 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
79 :class => 'icon-del' }
79 :class => 'icon-del' }
80 end
80 end
81
81
82 def test_context_menu_multiple_issues_of_different_project
82 def test_context_menu_multiple_issues_of_different_projects
83 @request.session[:user_id] = 2
83 @request.session[:user_id] = 2
84 get :issues, :ids => [1, 2, 4]
84 get :issues, :ids => [1, 2, 6]
85 assert_response :success
85 assert_response :success
86 assert_template 'context_menu'
86 assert_template 'context_menu'
87 ids = "ids%5B%5D=1&amp;ids%5B%5D=2&amp;ids%5B%5D=6"
87 assert_tag :tag => 'a', :content => 'Delete',
88 assert_tag :tag => 'a', :content => 'Delete',
88 :attributes => { :href => '#',
89 :attributes => { :href => "/issues/destroy?#{ids}",
89 :class => 'icon-del disabled' }
90 :class => 'icon-del' }
90 end
91 end
91
92
92 end
93 end
@@ -1,1070 +1,1077
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_show_by_anonymous
234 def test_show_by_anonymous
235 get :show, :id => 1
235 get :show, :id => 1
236 assert_response :success
236 assert_response :success
237 assert_template 'show.rhtml'
237 assert_template 'show.rhtml'
238 assert_not_nil assigns(:issue)
238 assert_not_nil assigns(:issue)
239 assert_equal Issue.find(1), assigns(:issue)
239 assert_equal Issue.find(1), assigns(:issue)
240
240
241 # anonymous role is allowed to add a note
241 # anonymous role is allowed to add a note
242 assert_tag :tag => 'form',
242 assert_tag :tag => 'form',
243 :descendant => { :tag => 'fieldset',
243 :descendant => { :tag => 'fieldset',
244 :child => { :tag => 'legend',
244 :child => { :tag => 'legend',
245 :content => /Notes/ } }
245 :content => /Notes/ } }
246 end
246 end
247
247
248 def test_show_by_manager
248 def test_show_by_manager
249 @request.session[:user_id] = 2
249 @request.session[:user_id] = 2
250 get :show, :id => 1
250 get :show, :id => 1
251 assert_response :success
251 assert_response :success
252
252
253 assert_tag :tag => 'form',
253 assert_tag :tag => 'form',
254 :descendant => { :tag => 'fieldset',
254 :descendant => { :tag => 'fieldset',
255 :child => { :tag => 'legend',
255 :child => { :tag => 'legend',
256 :content => /Change properties/ } },
256 :content => /Change properties/ } },
257 :descendant => { :tag => 'fieldset',
257 :descendant => { :tag => 'fieldset',
258 :child => { :tag => 'legend',
258 :child => { :tag => 'legend',
259 :content => /Log time/ } },
259 :content => /Log time/ } },
260 :descendant => { :tag => 'fieldset',
260 :descendant => { :tag => 'fieldset',
261 :child => { :tag => 'legend',
261 :child => { :tag => 'legend',
262 :content => /Notes/ } }
262 :content => /Notes/ } }
263 end
263 end
264
264
265 def test_show_should_deny_anonymous_access_without_permission
265 def test_show_should_deny_anonymous_access_without_permission
266 Role.anonymous.remove_permission!(:view_issues)
266 Role.anonymous.remove_permission!(:view_issues)
267 get :show, :id => 1
267 get :show, :id => 1
268 assert_response :redirect
268 assert_response :redirect
269 end
269 end
270
270
271 def test_show_should_deny_non_member_access_without_permission
271 def test_show_should_deny_non_member_access_without_permission
272 Role.non_member.remove_permission!(:view_issues)
272 Role.non_member.remove_permission!(:view_issues)
273 @request.session[:user_id] = 9
273 @request.session[:user_id] = 9
274 get :show, :id => 1
274 get :show, :id => 1
275 assert_response 403
275 assert_response 403
276 end
276 end
277
277
278 def test_show_should_deny_member_access_without_permission
278 def test_show_should_deny_member_access_without_permission
279 Role.find(1).remove_permission!(:view_issues)
279 Role.find(1).remove_permission!(:view_issues)
280 @request.session[:user_id] = 2
280 @request.session[:user_id] = 2
281 get :show, :id => 1
281 get :show, :id => 1
282 assert_response 403
282 assert_response 403
283 end
283 end
284
284
285 def test_show_should_not_disclose_relations_to_invisible_issues
285 def test_show_should_not_disclose_relations_to_invisible_issues
286 Setting.cross_project_issue_relations = '1'
286 Setting.cross_project_issue_relations = '1'
287 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
287 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
288 # Relation to a private project issue
288 # Relation to a private project issue
289 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
289 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
290
290
291 get :show, :id => 1
291 get :show, :id => 1
292 assert_response :success
292 assert_response :success
293
293
294 assert_tag :div, :attributes => { :id => 'relations' },
294 assert_tag :div, :attributes => { :id => 'relations' },
295 :descendant => { :tag => 'a', :content => /#2$/ }
295 :descendant => { :tag => 'a', :content => /#2$/ }
296 assert_no_tag :div, :attributes => { :id => 'relations' },
296 assert_no_tag :div, :attributes => { :id => 'relations' },
297 :descendant => { :tag => 'a', :content => /#4$/ }
297 :descendant => { :tag => 'a', :content => /#4$/ }
298 end
298 end
299
299
300 def test_show_atom
300 def test_show_atom
301 get :show, :id => 2, :format => 'atom'
301 get :show, :id => 2, :format => 'atom'
302 assert_response :success
302 assert_response :success
303 assert_template 'journals/index.rxml'
303 assert_template 'journals/index.rxml'
304 # Inline image
304 # Inline image
305 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
305 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
306 end
306 end
307
307
308 def test_show_export_to_pdf
308 def test_show_export_to_pdf
309 get :show, :id => 3, :format => 'pdf'
309 get :show, :id => 3, :format => 'pdf'
310 assert_response :success
310 assert_response :success
311 assert_equal 'application/pdf', @response.content_type
311 assert_equal 'application/pdf', @response.content_type
312 assert @response.body.starts_with?('%PDF')
312 assert @response.body.starts_with?('%PDF')
313 assert_not_nil assigns(:issue)
313 assert_not_nil assigns(:issue)
314 end
314 end
315
315
316 def test_get_new
316 def test_get_new
317 @request.session[:user_id] = 2
317 @request.session[:user_id] = 2
318 get :new, :project_id => 1, :tracker_id => 1
318 get :new, :project_id => 1, :tracker_id => 1
319 assert_response :success
319 assert_response :success
320 assert_template 'new'
320 assert_template 'new'
321
321
322 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
322 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
323 :value => 'Default string' }
323 :value => 'Default string' }
324 end
324 end
325
325
326 def test_get_new_without_tracker_id
326 def test_get_new_without_tracker_id
327 @request.session[:user_id] = 2
327 @request.session[:user_id] = 2
328 get :new, :project_id => 1
328 get :new, :project_id => 1
329 assert_response :success
329 assert_response :success
330 assert_template 'new'
330 assert_template 'new'
331
331
332 issue = assigns(:issue)
332 issue = assigns(:issue)
333 assert_not_nil issue
333 assert_not_nil issue
334 assert_equal Project.find(1).trackers.first, issue.tracker
334 assert_equal Project.find(1).trackers.first, issue.tracker
335 end
335 end
336
336
337 def test_get_new_with_no_default_status_should_display_an_error
337 def test_get_new_with_no_default_status_should_display_an_error
338 @request.session[:user_id] = 2
338 @request.session[:user_id] = 2
339 IssueStatus.delete_all
339 IssueStatus.delete_all
340
340
341 get :new, :project_id => 1
341 get :new, :project_id => 1
342 assert_response 500
342 assert_response 500
343 assert_not_nil flash[:error]
343 assert_not_nil flash[:error]
344 assert_tag :tag => 'div', :attributes => { :class => /error/ },
344 assert_tag :tag => 'div', :attributes => { :class => /error/ },
345 :content => /No default issue/
345 :content => /No default issue/
346 end
346 end
347
347
348 def test_get_new_with_no_tracker_should_display_an_error
348 def test_get_new_with_no_tracker_should_display_an_error
349 @request.session[:user_id] = 2
349 @request.session[:user_id] = 2
350 Tracker.delete_all
350 Tracker.delete_all
351
351
352 get :new, :project_id => 1
352 get :new, :project_id => 1
353 assert_response 500
353 assert_response 500
354 assert_not_nil flash[:error]
354 assert_not_nil flash[:error]
355 assert_tag :tag => 'div', :attributes => { :class => /error/ },
355 assert_tag :tag => 'div', :attributes => { :class => /error/ },
356 :content => /No tracker/
356 :content => /No tracker/
357 end
357 end
358
358
359 def test_update_new_form
359 def test_update_new_form
360 @request.session[:user_id] = 2
360 @request.session[:user_id] = 2
361 xhr :post, :new, :project_id => 1,
361 xhr :post, :new, :project_id => 1,
362 :issue => {:tracker_id => 2,
362 :issue => {:tracker_id => 2,
363 :subject => 'This is the test_new issue',
363 :subject => 'This is the test_new issue',
364 :description => 'This is the description',
364 :description => 'This is the description',
365 :priority_id => 5}
365 :priority_id => 5}
366 assert_response :success
366 assert_response :success
367 assert_template 'attributes'
367 assert_template 'attributes'
368
368
369 issue = assigns(:issue)
369 issue = assigns(:issue)
370 assert_kind_of Issue, issue
370 assert_kind_of Issue, issue
371 assert_equal 1, issue.project_id
371 assert_equal 1, issue.project_id
372 assert_equal 2, issue.tracker_id
372 assert_equal 2, issue.tracker_id
373 assert_equal 'This is the test_new issue', issue.subject
373 assert_equal 'This is the test_new issue', issue.subject
374 end
374 end
375
375
376 def test_post_create
376 def test_post_create
377 @request.session[:user_id] = 2
377 @request.session[:user_id] = 2
378 assert_difference 'Issue.count' do
378 assert_difference 'Issue.count' do
379 post :create, :project_id => 1,
379 post :create, :project_id => 1,
380 :issue => {:tracker_id => 3,
380 :issue => {:tracker_id => 3,
381 :status_id => 2,
381 :status_id => 2,
382 :subject => 'This is the test_new issue',
382 :subject => 'This is the test_new issue',
383 :description => 'This is the description',
383 :description => 'This is the description',
384 :priority_id => 5,
384 :priority_id => 5,
385 :estimated_hours => '',
385 :estimated_hours => '',
386 :custom_field_values => {'2' => 'Value for field 2'}}
386 :custom_field_values => {'2' => 'Value for field 2'}}
387 end
387 end
388 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
388 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
389
389
390 issue = Issue.find_by_subject('This is the test_new issue')
390 issue = Issue.find_by_subject('This is the test_new issue')
391 assert_not_nil issue
391 assert_not_nil issue
392 assert_equal 2, issue.author_id
392 assert_equal 2, issue.author_id
393 assert_equal 3, issue.tracker_id
393 assert_equal 3, issue.tracker_id
394 assert_equal 2, issue.status_id
394 assert_equal 2, issue.status_id
395 assert_nil issue.estimated_hours
395 assert_nil issue.estimated_hours
396 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
396 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
397 assert_not_nil v
397 assert_not_nil v
398 assert_equal 'Value for field 2', v.value
398 assert_equal 'Value for field 2', v.value
399 end
399 end
400
400
401 def test_post_create_and_continue
401 def test_post_create_and_continue
402 @request.session[:user_id] = 2
402 @request.session[:user_id] = 2
403 post :create, :project_id => 1,
403 post :create, :project_id => 1,
404 :issue => {:tracker_id => 3,
404 :issue => {:tracker_id => 3,
405 :subject => 'This is first issue',
405 :subject => 'This is first issue',
406 :priority_id => 5},
406 :priority_id => 5},
407 :continue => ''
407 :continue => ''
408 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook',
408 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook',
409 :issue => {:tracker_id => 3}
409 :issue => {:tracker_id => 3}
410 end
410 end
411
411
412 def test_post_create_without_custom_fields_param
412 def test_post_create_without_custom_fields_param
413 @request.session[:user_id] = 2
413 @request.session[:user_id] = 2
414 assert_difference 'Issue.count' do
414 assert_difference 'Issue.count' do
415 post :create, :project_id => 1,
415 post :create, :project_id => 1,
416 :issue => {:tracker_id => 1,
416 :issue => {:tracker_id => 1,
417 :subject => 'This is the test_new issue',
417 :subject => 'This is the test_new issue',
418 :description => 'This is the description',
418 :description => 'This is the description',
419 :priority_id => 5}
419 :priority_id => 5}
420 end
420 end
421 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
421 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
422 end
422 end
423
423
424 def test_post_create_with_required_custom_field_and_without_custom_fields_param
424 def test_post_create_with_required_custom_field_and_without_custom_fields_param
425 field = IssueCustomField.find_by_name('Database')
425 field = IssueCustomField.find_by_name('Database')
426 field.update_attribute(:is_required, true)
426 field.update_attribute(:is_required, true)
427
427
428 @request.session[:user_id] = 2
428 @request.session[:user_id] = 2
429 post :create, :project_id => 1,
429 post :create, :project_id => 1,
430 :issue => {:tracker_id => 1,
430 :issue => {:tracker_id => 1,
431 :subject => 'This is the test_new issue',
431 :subject => 'This is the test_new issue',
432 :description => 'This is the description',
432 :description => 'This is the description',
433 :priority_id => 5}
433 :priority_id => 5}
434 assert_response :success
434 assert_response :success
435 assert_template 'new'
435 assert_template 'new'
436 issue = assigns(:issue)
436 issue = assigns(:issue)
437 assert_not_nil issue
437 assert_not_nil issue
438 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
438 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
439 end
439 end
440
440
441 def test_post_create_with_watchers
441 def test_post_create_with_watchers
442 @request.session[:user_id] = 2
442 @request.session[:user_id] = 2
443 ActionMailer::Base.deliveries.clear
443 ActionMailer::Base.deliveries.clear
444
444
445 assert_difference 'Watcher.count', 2 do
445 assert_difference 'Watcher.count', 2 do
446 post :create, :project_id => 1,
446 post :create, :project_id => 1,
447 :issue => {:tracker_id => 1,
447 :issue => {:tracker_id => 1,
448 :subject => 'This is a new issue with watchers',
448 :subject => 'This is a new issue with watchers',
449 :description => 'This is the description',
449 :description => 'This is the description',
450 :priority_id => 5,
450 :priority_id => 5,
451 :watcher_user_ids => ['2', '3']}
451 :watcher_user_ids => ['2', '3']}
452 end
452 end
453 issue = Issue.find_by_subject('This is a new issue with watchers')
453 issue = Issue.find_by_subject('This is a new issue with watchers')
454 assert_not_nil issue
454 assert_not_nil issue
455 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
455 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
456
456
457 # Watchers added
457 # Watchers added
458 assert_equal [2, 3], issue.watcher_user_ids.sort
458 assert_equal [2, 3], issue.watcher_user_ids.sort
459 assert issue.watched_by?(User.find(3))
459 assert issue.watched_by?(User.find(3))
460 # Watchers notified
460 # Watchers notified
461 mail = ActionMailer::Base.deliveries.last
461 mail = ActionMailer::Base.deliveries.last
462 assert_kind_of TMail::Mail, mail
462 assert_kind_of TMail::Mail, mail
463 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
463 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
464 end
464 end
465
465
466 def test_post_create_subissue
466 def test_post_create_subissue
467 @request.session[:user_id] = 2
467 @request.session[:user_id] = 2
468
468
469 assert_difference 'Issue.count' do
469 assert_difference 'Issue.count' do
470 post :create, :project_id => 1,
470 post :create, :project_id => 1,
471 :issue => {:tracker_id => 1,
471 :issue => {:tracker_id => 1,
472 :subject => 'This is a child issue',
472 :subject => 'This is a child issue',
473 :parent_issue_id => 2}
473 :parent_issue_id => 2}
474 end
474 end
475 issue = Issue.find_by_subject('This is a child issue')
475 issue = Issue.find_by_subject('This is a child issue')
476 assert_not_nil issue
476 assert_not_nil issue
477 assert_equal Issue.find(2), issue.parent
477 assert_equal Issue.find(2), issue.parent
478 end
478 end
479
479
480 def test_post_create_should_send_a_notification
480 def test_post_create_should_send_a_notification
481 ActionMailer::Base.deliveries.clear
481 ActionMailer::Base.deliveries.clear
482 @request.session[:user_id] = 2
482 @request.session[:user_id] = 2
483 assert_difference 'Issue.count' do
483 assert_difference 'Issue.count' do
484 post :create, :project_id => 1,
484 post :create, :project_id => 1,
485 :issue => {:tracker_id => 3,
485 :issue => {:tracker_id => 3,
486 :subject => 'This is the test_new issue',
486 :subject => 'This is the test_new issue',
487 :description => 'This is the description',
487 :description => 'This is the description',
488 :priority_id => 5,
488 :priority_id => 5,
489 :estimated_hours => '',
489 :estimated_hours => '',
490 :custom_field_values => {'2' => 'Value for field 2'}}
490 :custom_field_values => {'2' => 'Value for field 2'}}
491 end
491 end
492 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
492 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
493
493
494 assert_equal 1, ActionMailer::Base.deliveries.size
494 assert_equal 1, ActionMailer::Base.deliveries.size
495 end
495 end
496
496
497 def test_post_create_should_preserve_fields_values_on_validation_failure
497 def test_post_create_should_preserve_fields_values_on_validation_failure
498 @request.session[:user_id] = 2
498 @request.session[:user_id] = 2
499 post :create, :project_id => 1,
499 post :create, :project_id => 1,
500 :issue => {:tracker_id => 1,
500 :issue => {:tracker_id => 1,
501 # empty subject
501 # empty subject
502 :subject => '',
502 :subject => '',
503 :description => 'This is a description',
503 :description => 'This is a description',
504 :priority_id => 6,
504 :priority_id => 6,
505 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
505 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
506 assert_response :success
506 assert_response :success
507 assert_template 'new'
507 assert_template 'new'
508
508
509 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
509 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
510 :content => 'This is a description'
510 :content => 'This is a description'
511 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
511 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
512 :child => { :tag => 'option', :attributes => { :selected => 'selected',
512 :child => { :tag => 'option', :attributes => { :selected => 'selected',
513 :value => '6' },
513 :value => '6' },
514 :content => 'High' }
514 :content => 'High' }
515 # Custom fields
515 # Custom fields
516 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
516 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
517 :child => { :tag => 'option', :attributes => { :selected => 'selected',
517 :child => { :tag => 'option', :attributes => { :selected => 'selected',
518 :value => 'Oracle' },
518 :value => 'Oracle' },
519 :content => 'Oracle' }
519 :content => 'Oracle' }
520 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
520 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
521 :value => 'Value for field 2'}
521 :value => 'Value for field 2'}
522 end
522 end
523
523
524 def test_post_create_should_ignore_non_safe_attributes
524 def test_post_create_should_ignore_non_safe_attributes
525 @request.session[:user_id] = 2
525 @request.session[:user_id] = 2
526 assert_nothing_raised do
526 assert_nothing_raised do
527 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
527 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
528 end
528 end
529 end
529 end
530
530
531 context "without workflow privilege" do
531 context "without workflow privilege" do
532 setup do
532 setup do
533 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
533 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
534 Role.anonymous.add_permission! :add_issues
534 Role.anonymous.add_permission! :add_issues
535 end
535 end
536
536
537 context "#new" do
537 context "#new" do
538 should "propose default status only" do
538 should "propose default status only" do
539 get :new, :project_id => 1
539 get :new, :project_id => 1
540 assert_response :success
540 assert_response :success
541 assert_template 'new'
541 assert_template 'new'
542 assert_tag :tag => 'select',
542 assert_tag :tag => 'select',
543 :attributes => {:name => 'issue[status_id]'},
543 :attributes => {:name => 'issue[status_id]'},
544 :children => {:count => 1},
544 :children => {:count => 1},
545 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
545 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
546 end
546 end
547
547
548 should "accept default status" do
548 should "accept default status" do
549 assert_difference 'Issue.count' do
549 assert_difference 'Issue.count' do
550 post :create, :project_id => 1,
550 post :create, :project_id => 1,
551 :issue => {:tracker_id => 1,
551 :issue => {:tracker_id => 1,
552 :subject => 'This is an issue',
552 :subject => 'This is an issue',
553 :status_id => 1}
553 :status_id => 1}
554 end
554 end
555 issue = Issue.last(:order => 'id')
555 issue = Issue.last(:order => 'id')
556 assert_equal IssueStatus.default, issue.status
556 assert_equal IssueStatus.default, issue.status
557 end
557 end
558
558
559 should "ignore unauthorized status" do
559 should "ignore unauthorized status" do
560 assert_difference 'Issue.count' do
560 assert_difference 'Issue.count' do
561 post :create, :project_id => 1,
561 post :create, :project_id => 1,
562 :issue => {:tracker_id => 1,
562 :issue => {:tracker_id => 1,
563 :subject => 'This is an issue',
563 :subject => 'This is an issue',
564 :status_id => 3}
564 :status_id => 3}
565 end
565 end
566 issue = Issue.last(:order => 'id')
566 issue = Issue.last(:order => 'id')
567 assert_equal IssueStatus.default, issue.status
567 assert_equal IssueStatus.default, issue.status
568 end
568 end
569 end
569 end
570 end
570 end
571
571
572 def test_copy_issue
572 def test_copy_issue
573 @request.session[:user_id] = 2
573 @request.session[:user_id] = 2
574 get :new, :project_id => 1, :copy_from => 1
574 get :new, :project_id => 1, :copy_from => 1
575 assert_template 'new'
575 assert_template 'new'
576 assert_not_nil assigns(:issue)
576 assert_not_nil assigns(:issue)
577 orig = Issue.find(1)
577 orig = Issue.find(1)
578 assert_equal orig.subject, assigns(:issue).subject
578 assert_equal orig.subject, assigns(:issue).subject
579 end
579 end
580
580
581 def test_get_edit
581 def test_get_edit
582 @request.session[:user_id] = 2
582 @request.session[:user_id] = 2
583 get :edit, :id => 1
583 get :edit, :id => 1
584 assert_response :success
584 assert_response :success
585 assert_template 'edit'
585 assert_template 'edit'
586 assert_not_nil assigns(:issue)
586 assert_not_nil assigns(:issue)
587 assert_equal Issue.find(1), assigns(:issue)
587 assert_equal Issue.find(1), assigns(:issue)
588 end
588 end
589
589
590 def test_get_edit_with_params
590 def test_get_edit_with_params
591 @request.session[:user_id] = 2
591 @request.session[:user_id] = 2
592 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
592 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
593 assert_response :success
593 assert_response :success
594 assert_template 'edit'
594 assert_template 'edit'
595
595
596 issue = assigns(:issue)
596 issue = assigns(:issue)
597 assert_not_nil issue
597 assert_not_nil issue
598
598
599 assert_equal 5, issue.status_id
599 assert_equal 5, issue.status_id
600 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
600 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
601 :child => { :tag => 'option',
601 :child => { :tag => 'option',
602 :content => 'Closed',
602 :content => 'Closed',
603 :attributes => { :selected => 'selected' } }
603 :attributes => { :selected => 'selected' } }
604
604
605 assert_equal 7, issue.priority_id
605 assert_equal 7, issue.priority_id
606 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
606 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
607 :child => { :tag => 'option',
607 :child => { :tag => 'option',
608 :content => 'Urgent',
608 :content => 'Urgent',
609 :attributes => { :selected => 'selected' } }
609 :attributes => { :selected => 'selected' } }
610 end
610 end
611
611
612 def test_update_edit_form
612 def test_update_edit_form
613 @request.session[:user_id] = 2
613 @request.session[:user_id] = 2
614 xhr :post, :new, :project_id => 1,
614 xhr :post, :new, :project_id => 1,
615 :id => 1,
615 :id => 1,
616 :issue => {:tracker_id => 2,
616 :issue => {:tracker_id => 2,
617 :subject => 'This is the test_new issue',
617 :subject => 'This is the test_new issue',
618 :description => 'This is the description',
618 :description => 'This is the description',
619 :priority_id => 5}
619 :priority_id => 5}
620 assert_response :success
620 assert_response :success
621 assert_template 'attributes'
621 assert_template 'attributes'
622
622
623 issue = assigns(:issue)
623 issue = assigns(:issue)
624 assert_kind_of Issue, issue
624 assert_kind_of Issue, issue
625 assert_equal 1, issue.id
625 assert_equal 1, issue.id
626 assert_equal 1, issue.project_id
626 assert_equal 1, issue.project_id
627 assert_equal 2, issue.tracker_id
627 assert_equal 2, issue.tracker_id
628 assert_equal 'This is the test_new issue', issue.subject
628 assert_equal 'This is the test_new issue', issue.subject
629 end
629 end
630
630
631 def test_update_using_invalid_http_verbs
631 def test_update_using_invalid_http_verbs
632 @request.session[:user_id] = 2
632 @request.session[:user_id] = 2
633 subject = 'Updated by an invalid http verb'
633 subject = 'Updated by an invalid http verb'
634
634
635 get :update, :id => 1, :issue => {:subject => subject}
635 get :update, :id => 1, :issue => {:subject => subject}
636 assert_not_equal subject, Issue.find(1).subject
636 assert_not_equal subject, Issue.find(1).subject
637
637
638 post :update, :id => 1, :issue => {:subject => subject}
638 post :update, :id => 1, :issue => {:subject => subject}
639 assert_not_equal subject, Issue.find(1).subject
639 assert_not_equal subject, Issue.find(1).subject
640
640
641 delete :update, :id => 1, :issue => {:subject => subject}
641 delete :update, :id => 1, :issue => {:subject => subject}
642 assert_not_equal subject, Issue.find(1).subject
642 assert_not_equal subject, Issue.find(1).subject
643 end
643 end
644
644
645 def test_put_update_without_custom_fields_param
645 def test_put_update_without_custom_fields_param
646 @request.session[:user_id] = 2
646 @request.session[:user_id] = 2
647 ActionMailer::Base.deliveries.clear
647 ActionMailer::Base.deliveries.clear
648
648
649 issue = Issue.find(1)
649 issue = Issue.find(1)
650 assert_equal '125', issue.custom_value_for(2).value
650 assert_equal '125', issue.custom_value_for(2).value
651 old_subject = issue.subject
651 old_subject = issue.subject
652 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
652 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
653
653
654 assert_difference('Journal.count') do
654 assert_difference('Journal.count') do
655 assert_difference('JournalDetail.count', 2) do
655 assert_difference('JournalDetail.count', 2) do
656 put :update, :id => 1, :issue => {:subject => new_subject,
656 put :update, :id => 1, :issue => {:subject => new_subject,
657 :priority_id => '6',
657 :priority_id => '6',
658 :category_id => '1' # no change
658 :category_id => '1' # no change
659 }
659 }
660 end
660 end
661 end
661 end
662 assert_redirected_to :action => 'show', :id => '1'
662 assert_redirected_to :action => 'show', :id => '1'
663 issue.reload
663 issue.reload
664 assert_equal new_subject, issue.subject
664 assert_equal new_subject, issue.subject
665 # Make sure custom fields were not cleared
665 # Make sure custom fields were not cleared
666 assert_equal '125', issue.custom_value_for(2).value
666 assert_equal '125', issue.custom_value_for(2).value
667
667
668 mail = ActionMailer::Base.deliveries.last
668 mail = ActionMailer::Base.deliveries.last
669 assert_kind_of TMail::Mail, mail
669 assert_kind_of TMail::Mail, mail
670 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
670 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
671 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
671 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
672 end
672 end
673
673
674 def test_put_update_with_custom_field_change
674 def test_put_update_with_custom_field_change
675 @request.session[:user_id] = 2
675 @request.session[:user_id] = 2
676 issue = Issue.find(1)
676 issue = Issue.find(1)
677 assert_equal '125', issue.custom_value_for(2).value
677 assert_equal '125', issue.custom_value_for(2).value
678
678
679 assert_difference('Journal.count') do
679 assert_difference('Journal.count') do
680 assert_difference('JournalDetail.count', 3) do
680 assert_difference('JournalDetail.count', 3) do
681 put :update, :id => 1, :issue => {:subject => 'Custom field change',
681 put :update, :id => 1, :issue => {:subject => 'Custom field change',
682 :priority_id => '6',
682 :priority_id => '6',
683 :category_id => '1', # no change
683 :category_id => '1', # no change
684 :custom_field_values => { '2' => 'New custom value' }
684 :custom_field_values => { '2' => 'New custom value' }
685 }
685 }
686 end
686 end
687 end
687 end
688 assert_redirected_to :action => 'show', :id => '1'
688 assert_redirected_to :action => 'show', :id => '1'
689 issue.reload
689 issue.reload
690 assert_equal 'New custom value', issue.custom_value_for(2).value
690 assert_equal 'New custom value', issue.custom_value_for(2).value
691
691
692 mail = ActionMailer::Base.deliveries.last
692 mail = ActionMailer::Base.deliveries.last
693 assert_kind_of TMail::Mail, mail
693 assert_kind_of TMail::Mail, mail
694 assert mail.body.include?("Searchable field changed from 125 to New custom value")
694 assert mail.body.include?("Searchable field changed from 125 to New custom value")
695 end
695 end
696
696
697 def test_put_update_with_status_and_assignee_change
697 def test_put_update_with_status_and_assignee_change
698 issue = Issue.find(1)
698 issue = Issue.find(1)
699 assert_equal 1, issue.status_id
699 assert_equal 1, issue.status_id
700 @request.session[:user_id] = 2
700 @request.session[:user_id] = 2
701 assert_difference('TimeEntry.count', 0) do
701 assert_difference('TimeEntry.count', 0) do
702 put :update,
702 put :update,
703 :id => 1,
703 :id => 1,
704 :issue => { :status_id => 2, :assigned_to_id => 3 },
704 :issue => { :status_id => 2, :assigned_to_id => 3 },
705 :notes => 'Assigned to dlopper',
705 :notes => 'Assigned to dlopper',
706 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
706 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
707 end
707 end
708 assert_redirected_to :action => 'show', :id => '1'
708 assert_redirected_to :action => 'show', :id => '1'
709 issue.reload
709 issue.reload
710 assert_equal 2, issue.status_id
710 assert_equal 2, issue.status_id
711 j = Journal.find(:first, :order => 'id DESC')
711 j = Journal.find(:first, :order => 'id DESC')
712 assert_equal 'Assigned to dlopper', j.notes
712 assert_equal 'Assigned to dlopper', j.notes
713 assert_equal 2, j.details.size
713 assert_equal 2, j.details.size
714
714
715 mail = ActionMailer::Base.deliveries.last
715 mail = ActionMailer::Base.deliveries.last
716 assert mail.body.include?("Status changed from New to Assigned")
716 assert mail.body.include?("Status changed from New to Assigned")
717 # subject should contain the new status
717 # subject should contain the new status
718 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
718 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
719 end
719 end
720
720
721 def test_put_update_with_note_only
721 def test_put_update_with_note_only
722 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
722 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
723 # anonymous user
723 # anonymous user
724 put :update,
724 put :update,
725 :id => 1,
725 :id => 1,
726 :notes => notes
726 :notes => notes
727 assert_redirected_to :action => 'show', :id => '1'
727 assert_redirected_to :action => 'show', :id => '1'
728 j = Journal.find(:first, :order => 'id DESC')
728 j = Journal.find(:first, :order => 'id DESC')
729 assert_equal notes, j.notes
729 assert_equal notes, j.notes
730 assert_equal 0, j.details.size
730 assert_equal 0, j.details.size
731 assert_equal User.anonymous, j.user
731 assert_equal User.anonymous, j.user
732
732
733 mail = ActionMailer::Base.deliveries.last
733 mail = ActionMailer::Base.deliveries.last
734 assert mail.body.include?(notes)
734 assert mail.body.include?(notes)
735 end
735 end
736
736
737 def test_put_update_with_note_and_spent_time
737 def test_put_update_with_note_and_spent_time
738 @request.session[:user_id] = 2
738 @request.session[:user_id] = 2
739 spent_hours_before = Issue.find(1).spent_hours
739 spent_hours_before = Issue.find(1).spent_hours
740 assert_difference('TimeEntry.count') do
740 assert_difference('TimeEntry.count') do
741 put :update,
741 put :update,
742 :id => 1,
742 :id => 1,
743 :notes => '2.5 hours added',
743 :notes => '2.5 hours added',
744 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
744 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
745 end
745 end
746 assert_redirected_to :action => 'show', :id => '1'
746 assert_redirected_to :action => 'show', :id => '1'
747
747
748 issue = Issue.find(1)
748 issue = Issue.find(1)
749
749
750 j = Journal.find(:first, :order => 'id DESC')
750 j = Journal.find(:first, :order => 'id DESC')
751 assert_equal '2.5 hours added', j.notes
751 assert_equal '2.5 hours added', j.notes
752 assert_equal 0, j.details.size
752 assert_equal 0, j.details.size
753
753
754 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
754 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
755 assert_not_nil t
755 assert_not_nil t
756 assert_equal 2.5, t.hours
756 assert_equal 2.5, t.hours
757 assert_equal spent_hours_before + 2.5, issue.spent_hours
757 assert_equal spent_hours_before + 2.5, issue.spent_hours
758 end
758 end
759
759
760 def test_put_update_with_attachment_only
760 def test_put_update_with_attachment_only
761 set_tmp_attachments_directory
761 set_tmp_attachments_directory
762
762
763 # Delete all fixtured journals, a race condition can occur causing the wrong
763 # Delete all fixtured journals, a race condition can occur causing the wrong
764 # journal to get fetched in the next find.
764 # journal to get fetched in the next find.
765 Journal.delete_all
765 Journal.delete_all
766
766
767 # anonymous user
767 # anonymous user
768 put :update,
768 put :update,
769 :id => 1,
769 :id => 1,
770 :notes => '',
770 :notes => '',
771 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
771 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
772 assert_redirected_to :action => 'show', :id => '1'
772 assert_redirected_to :action => 'show', :id => '1'
773 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
773 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
774 assert j.notes.blank?
774 assert j.notes.blank?
775 assert_equal 1, j.details.size
775 assert_equal 1, j.details.size
776 assert_equal 'testfile.txt', j.details.first.value
776 assert_equal 'testfile.txt', j.details.first.value
777 assert_equal User.anonymous, j.user
777 assert_equal User.anonymous, j.user
778
778
779 mail = ActionMailer::Base.deliveries.last
779 mail = ActionMailer::Base.deliveries.last
780 assert mail.body.include?('testfile.txt')
780 assert mail.body.include?('testfile.txt')
781 end
781 end
782
782
783 def test_put_update_with_attachment_that_fails_to_save
783 def test_put_update_with_attachment_that_fails_to_save
784 set_tmp_attachments_directory
784 set_tmp_attachments_directory
785
785
786 # Delete all fixtured journals, a race condition can occur causing the wrong
786 # Delete all fixtured journals, a race condition can occur causing the wrong
787 # journal to get fetched in the next find.
787 # journal to get fetched in the next find.
788 Journal.delete_all
788 Journal.delete_all
789
789
790 # Mock out the unsaved attachment
790 # Mock out the unsaved attachment
791 Attachment.any_instance.stubs(:create).returns(Attachment.new)
791 Attachment.any_instance.stubs(:create).returns(Attachment.new)
792
792
793 # anonymous user
793 # anonymous user
794 put :update,
794 put :update,
795 :id => 1,
795 :id => 1,
796 :notes => '',
796 :notes => '',
797 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
797 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
798 assert_redirected_to :action => 'show', :id => '1'
798 assert_redirected_to :action => 'show', :id => '1'
799 assert_equal '1 file(s) could not be saved.', flash[:warning]
799 assert_equal '1 file(s) could not be saved.', flash[:warning]
800
800
801 end if Object.const_defined?(:Mocha)
801 end if Object.const_defined?(:Mocha)
802
802
803 def test_put_update_with_no_change
803 def test_put_update_with_no_change
804 issue = Issue.find(1)
804 issue = Issue.find(1)
805 issue.journals.clear
805 issue.journals.clear
806 ActionMailer::Base.deliveries.clear
806 ActionMailer::Base.deliveries.clear
807
807
808 put :update,
808 put :update,
809 :id => 1,
809 :id => 1,
810 :notes => ''
810 :notes => ''
811 assert_redirected_to :action => 'show', :id => '1'
811 assert_redirected_to :action => 'show', :id => '1'
812
812
813 issue.reload
813 issue.reload
814 assert issue.journals.empty?
814 assert issue.journals.empty?
815 # No email should be sent
815 # No email should be sent
816 assert ActionMailer::Base.deliveries.empty?
816 assert ActionMailer::Base.deliveries.empty?
817 end
817 end
818
818
819 def test_put_update_should_send_a_notification
819 def test_put_update_should_send_a_notification
820 @request.session[:user_id] = 2
820 @request.session[:user_id] = 2
821 ActionMailer::Base.deliveries.clear
821 ActionMailer::Base.deliveries.clear
822 issue = Issue.find(1)
822 issue = Issue.find(1)
823 old_subject = issue.subject
823 old_subject = issue.subject
824 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
824 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
825
825
826 put :update, :id => 1, :issue => {:subject => new_subject,
826 put :update, :id => 1, :issue => {:subject => new_subject,
827 :priority_id => '6',
827 :priority_id => '6',
828 :category_id => '1' # no change
828 :category_id => '1' # no change
829 }
829 }
830 assert_equal 1, ActionMailer::Base.deliveries.size
830 assert_equal 1, ActionMailer::Base.deliveries.size
831 end
831 end
832
832
833 def test_put_update_with_invalid_spent_time
833 def test_put_update_with_invalid_spent_time
834 @request.session[:user_id] = 2
834 @request.session[:user_id] = 2
835 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
835 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
836
836
837 assert_no_difference('Journal.count') do
837 assert_no_difference('Journal.count') do
838 put :update,
838 put :update,
839 :id => 1,
839 :id => 1,
840 :notes => notes,
840 :notes => notes,
841 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
841 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
842 end
842 end
843 assert_response :success
843 assert_response :success
844 assert_template 'edit'
844 assert_template 'edit'
845
845
846 assert_tag :textarea, :attributes => { :name => 'notes' },
846 assert_tag :textarea, :attributes => { :name => 'notes' },
847 :content => notes
847 :content => notes
848 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
848 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
849 end
849 end
850
850
851 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
851 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
852 issue = Issue.find(2)
852 issue = Issue.find(2)
853 @request.session[:user_id] = 2
853 @request.session[:user_id] = 2
854
854
855 put :update,
855 put :update,
856 :id => issue.id,
856 :id => issue.id,
857 :issue => {
857 :issue => {
858 :fixed_version_id => 4
858 :fixed_version_id => 4
859 }
859 }
860
860
861 assert_response :redirect
861 assert_response :redirect
862 issue.reload
862 issue.reload
863 assert_equal 4, issue.fixed_version_id
863 assert_equal 4, issue.fixed_version_id
864 assert_not_equal issue.project_id, issue.fixed_version.project_id
864 assert_not_equal issue.project_id, issue.fixed_version.project_id
865 end
865 end
866
866
867 def test_put_update_should_redirect_back_using_the_back_url_parameter
867 def test_put_update_should_redirect_back_using_the_back_url_parameter
868 issue = Issue.find(2)
868 issue = Issue.find(2)
869 @request.session[:user_id] = 2
869 @request.session[:user_id] = 2
870
870
871 put :update,
871 put :update,
872 :id => issue.id,
872 :id => issue.id,
873 :issue => {
873 :issue => {
874 :fixed_version_id => 4
874 :fixed_version_id => 4
875 },
875 },
876 :back_url => '/issues'
876 :back_url => '/issues'
877
877
878 assert_response :redirect
878 assert_response :redirect
879 assert_redirected_to '/issues'
879 assert_redirected_to '/issues'
880 end
880 end
881
881
882 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
882 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
883 issue = Issue.find(2)
883 issue = Issue.find(2)
884 @request.session[:user_id] = 2
884 @request.session[:user_id] = 2
885
885
886 put :update,
886 put :update,
887 :id => issue.id,
887 :id => issue.id,
888 :issue => {
888 :issue => {
889 :fixed_version_id => 4
889 :fixed_version_id => 4
890 },
890 },
891 :back_url => 'http://google.com'
891 :back_url => 'http://google.com'
892
892
893 assert_response :redirect
893 assert_response :redirect
894 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
894 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
895 end
895 end
896
896
897 def test_get_bulk_edit
897 def test_get_bulk_edit
898 @request.session[:user_id] = 2
898 @request.session[:user_id] = 2
899 get :bulk_edit, :ids => [1, 2]
899 get :bulk_edit, :ids => [1, 2]
900 assert_response :success
900 assert_response :success
901 assert_template 'bulk_edit'
901 assert_template 'bulk_edit'
902
902
903 # Project specific custom field, date type
903 # Project specific custom field, date type
904 field = CustomField.find(9)
904 field = CustomField.find(9)
905 assert !field.is_for_all?
905 assert !field.is_for_all?
906 assert_equal 'date', field.field_format
906 assert_equal 'date', field.field_format
907 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
907 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
908
908
909 # System wide custom field
909 # System wide custom field
910 assert CustomField.find(1).is_for_all?
910 assert CustomField.find(1).is_for_all?
911 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
911 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
912 end
912 end
913
913
914 def test_bulk_update
914 def test_bulk_update
915 @request.session[:user_id] = 2
915 @request.session[:user_id] = 2
916 # update issues priority
916 # update issues priority
917 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
917 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
918 :issue => {:priority_id => 7,
918 :issue => {:priority_id => 7,
919 :assigned_to_id => '',
919 :assigned_to_id => '',
920 :custom_field_values => {'2' => ''}}
920 :custom_field_values => {'2' => ''}}
921
921
922 assert_response 302
922 assert_response 302
923 # check that the issues were updated
923 # check that the issues were updated
924 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
924 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
925
925
926 issue = Issue.find(1)
926 issue = Issue.find(1)
927 journal = issue.journals.find(:first, :order => 'created_on DESC')
927 journal = issue.journals.find(:first, :order => 'created_on DESC')
928 assert_equal '125', issue.custom_value_for(2).value
928 assert_equal '125', issue.custom_value_for(2).value
929 assert_equal 'Bulk editing', journal.notes
929 assert_equal 'Bulk editing', journal.notes
930 assert_equal 1, journal.details.size
930 assert_equal 1, journal.details.size
931 end
931 end
932
932
933 def test_bullk_update_should_send_a_notification
933 def test_bullk_update_should_send_a_notification
934 @request.session[:user_id] = 2
934 @request.session[:user_id] = 2
935 ActionMailer::Base.deliveries.clear
935 ActionMailer::Base.deliveries.clear
936 post(:bulk_update,
936 post(:bulk_update,
937 {
937 {
938 :ids => [1, 2],
938 :ids => [1, 2],
939 :notes => 'Bulk editing',
939 :notes => 'Bulk editing',
940 :issue => {
940 :issue => {
941 :priority_id => 7,
941 :priority_id => 7,
942 :assigned_to_id => '',
942 :assigned_to_id => '',
943 :custom_field_values => {'2' => ''}
943 :custom_field_values => {'2' => ''}
944 }
944 }
945 })
945 })
946
946
947 assert_response 302
947 assert_response 302
948 assert_equal 2, ActionMailer::Base.deliveries.size
948 assert_equal 2, ActionMailer::Base.deliveries.size
949 end
949 end
950
950
951 def test_bulk_update_status
951 def test_bulk_update_status
952 @request.session[:user_id] = 2
952 @request.session[:user_id] = 2
953 # update issues priority
953 # update issues priority
954 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
954 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
955 :issue => {:priority_id => '',
955 :issue => {:priority_id => '',
956 :assigned_to_id => '',
956 :assigned_to_id => '',
957 :status_id => '5'}
957 :status_id => '5'}
958
958
959 assert_response 302
959 assert_response 302
960 issue = Issue.find(1)
960 issue = Issue.find(1)
961 assert issue.closed?
961 assert issue.closed?
962 end
962 end
963
963
964 def test_bulk_update_custom_field
964 def test_bulk_update_custom_field
965 @request.session[:user_id] = 2
965 @request.session[:user_id] = 2
966 # update issues priority
966 # update issues priority
967 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
967 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
968 :issue => {:priority_id => '',
968 :issue => {:priority_id => '',
969 :assigned_to_id => '',
969 :assigned_to_id => '',
970 :custom_field_values => {'2' => '777'}}
970 :custom_field_values => {'2' => '777'}}
971
971
972 assert_response 302
972 assert_response 302
973
973
974 issue = Issue.find(1)
974 issue = Issue.find(1)
975 journal = issue.journals.find(:first, :order => 'created_on DESC')
975 journal = issue.journals.find(:first, :order => 'created_on DESC')
976 assert_equal '777', issue.custom_value_for(2).value
976 assert_equal '777', issue.custom_value_for(2).value
977 assert_equal 1, journal.details.size
977 assert_equal 1, journal.details.size
978 assert_equal '125', journal.details.first.old_value
978 assert_equal '125', journal.details.first.old_value
979 assert_equal '777', journal.details.first.value
979 assert_equal '777', journal.details.first.value
980 end
980 end
981
981
982 def test_bulk_update_unassign
982 def test_bulk_update_unassign
983 assert_not_nil Issue.find(2).assigned_to
983 assert_not_nil Issue.find(2).assigned_to
984 @request.session[:user_id] = 2
984 @request.session[:user_id] = 2
985 # unassign issues
985 # unassign issues
986 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
986 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
987 assert_response 302
987 assert_response 302
988 # check that the issues were updated
988 # check that the issues were updated
989 assert_nil Issue.find(2).assigned_to
989 assert_nil Issue.find(2).assigned_to
990 end
990 end
991
991
992 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
992 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
993 @request.session[:user_id] = 2
993 @request.session[:user_id] = 2
994
994
995 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
995 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
996
996
997 assert_response :redirect
997 assert_response :redirect
998 issues = Issue.find([1,2])
998 issues = Issue.find([1,2])
999 issues.each do |issue|
999 issues.each do |issue|
1000 assert_equal 4, issue.fixed_version_id
1000 assert_equal 4, issue.fixed_version_id
1001 assert_not_equal issue.project_id, issue.fixed_version.project_id
1001 assert_not_equal issue.project_id, issue.fixed_version.project_id
1002 end
1002 end
1003 end
1003 end
1004
1004
1005 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1005 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1006 @request.session[:user_id] = 2
1006 @request.session[:user_id] = 2
1007 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1007 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1008
1008
1009 assert_response :redirect
1009 assert_response :redirect
1010 assert_redirected_to '/issues'
1010 assert_redirected_to '/issues'
1011 end
1011 end
1012
1012
1013 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1013 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1014 @request.session[:user_id] = 2
1014 @request.session[:user_id] = 2
1015 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1015 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1016
1016
1017 assert_response :redirect
1017 assert_response :redirect
1018 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1018 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1019 end
1019 end
1020
1020
1021 def test_destroy_issue_with_no_time_entries
1021 def test_destroy_issue_with_no_time_entries
1022 assert_nil TimeEntry.find_by_issue_id(2)
1022 assert_nil TimeEntry.find_by_issue_id(2)
1023 @request.session[:user_id] = 2
1023 @request.session[:user_id] = 2
1024 post :destroy, :id => 2
1024 post :destroy, :id => 2
1025 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1025 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1026 assert_nil Issue.find_by_id(2)
1026 assert_nil Issue.find_by_id(2)
1027 end
1027 end
1028
1028
1029 def test_destroy_issues_with_time_entries
1029 def test_destroy_issues_with_time_entries
1030 @request.session[:user_id] = 2
1030 @request.session[:user_id] = 2
1031 post :destroy, :ids => [1, 3]
1031 post :destroy, :ids => [1, 3]
1032 assert_response :success
1032 assert_response :success
1033 assert_template 'destroy'
1033 assert_template 'destroy'
1034 assert_not_nil assigns(:hours)
1034 assert_not_nil assigns(:hours)
1035 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1035 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1036 end
1036 end
1037
1037
1038 def test_destroy_issues_and_destroy_time_entries
1038 def test_destroy_issues_and_destroy_time_entries
1039 @request.session[:user_id] = 2
1039 @request.session[:user_id] = 2
1040 post :destroy, :ids => [1, 3], :todo => 'destroy'
1040 post :destroy, :ids => [1, 3], :todo => 'destroy'
1041 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1041 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1042 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1042 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1043 assert_nil TimeEntry.find_by_id([1, 2])
1043 assert_nil TimeEntry.find_by_id([1, 2])
1044 end
1044 end
1045
1045
1046 def test_destroy_issues_and_assign_time_entries_to_project
1046 def test_destroy_issues_and_assign_time_entries_to_project
1047 @request.session[:user_id] = 2
1047 @request.session[:user_id] = 2
1048 post :destroy, :ids => [1, 3], :todo => 'nullify'
1048 post :destroy, :ids => [1, 3], :todo => 'nullify'
1049 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1049 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1050 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1050 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1051 assert_nil TimeEntry.find(1).issue_id
1051 assert_nil TimeEntry.find(1).issue_id
1052 assert_nil TimeEntry.find(2).issue_id
1052 assert_nil TimeEntry.find(2).issue_id
1053 end
1053 end
1054
1054
1055 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1055 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1056 @request.session[:user_id] = 2
1056 @request.session[:user_id] = 2
1057 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1057 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1058 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1058 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1059 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1059 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1060 assert_equal 2, TimeEntry.find(1).issue_id
1060 assert_equal 2, TimeEntry.find(1).issue_id
1061 assert_equal 2, TimeEntry.find(2).issue_id
1061 assert_equal 2, TimeEntry.find(2).issue_id
1062 end
1062 end
1063
1063
1064 def test_destroy_issues_from_different_projects
1065 @request.session[:user_id] = 2
1066 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1067 assert_redirected_to :controller => 'issues', :action => 'index'
1068 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1069 end
1070
1064 def test_default_search_scope
1071 def test_default_search_scope
1065 get :index
1072 get :index
1066 assert_tag :div, :attributes => {:id => 'quick-search'},
1073 assert_tag :div, :attributes => {:id => 'quick-search'},
1067 :child => {:tag => 'form',
1074 :child => {:tag => 'form',
1068 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1075 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1069 end
1076 end
1070 end
1077 end
General Comments 0
You need to be logged in to leave comments. Login now