##// END OF EJS Templates
Fixed that updating the issue form was broken by r4011 when user is not allowed to add issues (#13188)....
Jean-Philippe Lang -
r11175:7799788b3de6
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,435 +1,439
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssuesController < ApplicationController
19 19 menu_item :new_issue, :only => [:new, :create]
20 20 default_search_scope :issues
21 21
22 22 before_filter :find_issue, :only => [:show, :edit, :update]
23 23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :find_project, :only => [:new, :create]
24 before_filter :find_project, :only => [:new, :create, :update_form]
25 25 before_filter :authorize, :except => [:index]
26 26 before_filter :find_optional_project, :only => [:index]
27 27 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form]
29 29 accept_rss_auth :index, :show
30 30 accept_api_auth :index, :show, :create, :update, :destroy
31 31
32 32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33 33
34 34 helper :journals
35 35 helper :projects
36 36 include ProjectsHelper
37 37 helper :custom_fields
38 38 include CustomFieldsHelper
39 39 helper :issue_relations
40 40 include IssueRelationsHelper
41 41 helper :watchers
42 42 include WatchersHelper
43 43 helper :attachments
44 44 include AttachmentsHelper
45 45 helper :queries
46 46 include QueriesHelper
47 47 helper :repositories
48 48 include RepositoriesHelper
49 49 helper :sort
50 50 include SortHelper
51 51 include IssuesHelper
52 52 helper :timelog
53 53 include Redmine::Export::PDF
54 54
55 55 def index
56 56 retrieve_query
57 57 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
58 58 sort_update(@query.sortable_columns)
59 59 @query.sort_criteria = sort_criteria.to_a
60 60
61 61 if @query.valid?
62 62 case params[:format]
63 63 when 'csv', 'pdf'
64 64 @limit = Setting.issues_export_limit.to_i
65 65 when 'atom'
66 66 @limit = Setting.feeds_limit.to_i
67 67 when 'xml', 'json'
68 68 @offset, @limit = api_offset_and_limit
69 69 else
70 70 @limit = per_page_option
71 71 end
72 72
73 73 @issue_count = @query.issue_count
74 74 @issue_pages = Paginator.new @issue_count, @limit, params['page']
75 75 @offset ||= @issue_pages.offset
76 76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
77 77 :order => sort_clause,
78 78 :offset => @offset,
79 79 :limit => @limit)
80 80 @issue_count_by_group = @query.issue_count_by_group
81 81
82 82 respond_to do |format|
83 83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
84 84 format.api {
85 85 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
86 86 }
87 87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
88 88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
89 89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
90 90 end
91 91 else
92 92 respond_to do |format|
93 93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
94 94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
95 95 format.api { render_validation_errors(@query) }
96 96 end
97 97 end
98 98 rescue ActiveRecord::RecordNotFound
99 99 render_404
100 100 end
101 101
102 102 def show
103 103 @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
104 104 @journals.each_with_index {|j,i| j.indice = i+1}
105 105 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
106 106 @journals.reverse! if User.current.wants_comments_in_reverse_order?
107 107
108 108 @changesets = @issue.changesets.visible.all
109 109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
110 110
111 111 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
112 112 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
113 113 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
114 114 @priorities = IssuePriority.active
115 115 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
116 116 respond_to do |format|
117 117 format.html {
118 118 retrieve_previous_and_next_issue_ids
119 119 render :template => 'issues/show'
120 120 }
121 121 format.api
122 122 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
123 123 format.pdf {
124 124 pdf = issue_to_pdf(@issue, :journals => @journals)
125 125 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
126 126 }
127 127 end
128 128 end
129 129
130 130 # Add a new issue
131 131 # The new issue will be created from an existing one if copy_from parameter is given
132 132 def new
133 133 respond_to do |format|
134 134 format.html { render :action => 'new', :layout => !request.xhr? }
135 format.js { render :partial => 'update_form' }
136 135 end
137 136 end
138 137
139 138 def create
140 139 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
141 140 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
142 141 if @issue.save
143 142 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
144 143 respond_to do |format|
145 144 format.html {
146 145 render_attachment_warning_if_needed(@issue)
147 146 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
148 147 if params[:continue]
149 148 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
150 149 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
151 150 else
152 151 redirect_to issue_path(@issue)
153 152 end
154 153 }
155 154 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
156 155 end
157 156 return
158 157 else
159 158 respond_to do |format|
160 159 format.html { render :action => 'new' }
161 160 format.api { render_validation_errors(@issue) }
162 161 end
163 162 end
164 163 end
165 164
166 165 def edit
167 166 return unless update_issue_from_params
168 167
169 168 respond_to do |format|
170 169 format.html { }
171 170 format.xml { }
172 171 end
173 172 end
174 173
175 174 def update
176 175 return unless update_issue_from_params
177 176 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
178 177 saved = false
179 178 begin
180 179 saved = @issue.save_issue_with_child_records(params, @time_entry)
181 180 rescue ActiveRecord::StaleObjectError
182 181 @conflict = true
183 182 if params[:last_journal_id]
184 183 @conflict_journals = @issue.journals_after(params[:last_journal_id]).all
185 184 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
186 185 end
187 186 end
188 187
189 188 if saved
190 189 render_attachment_warning_if_needed(@issue)
191 190 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
192 191
193 192 respond_to do |format|
194 193 format.html { redirect_back_or_default issue_path(@issue) }
195 194 format.api { render_api_ok }
196 195 end
197 196 else
198 197 respond_to do |format|
199 198 format.html { render :action => 'edit' }
200 199 format.api { render_validation_errors(@issue) }
201 200 end
202 201 end
203 202 end
204 203
204 # Updates the issue form when changing the project, status or tracker
205 # on issue creation/update
206 def update_form
207 end
208
205 209 # Bulk edit/copy a set of issues
206 210 def bulk_edit
207 211 @issues.sort!
208 212 @copy = params[:copy].present?
209 213 @notes = params[:notes]
210 214
211 215 if User.current.allowed_to?(:move_issues, @projects)
212 216 @allowed_projects = Issue.allowed_target_projects_on_move
213 217 if params[:issue]
214 218 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
215 219 if @target_project
216 220 target_projects = [@target_project]
217 221 end
218 222 end
219 223 end
220 224 target_projects ||= @projects
221 225
222 226 if @copy
223 227 @available_statuses = [IssueStatus.default]
224 228 else
225 229 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
226 230 end
227 231 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
228 232 @assignables = target_projects.map(&:assignable_users).reduce(:&)
229 233 @trackers = target_projects.map(&:trackers).reduce(:&)
230 234 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
231 235 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
232 236 if @copy
233 237 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
234 238 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
235 239 end
236 240
237 241 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
238 242 render :layout => false if request.xhr?
239 243 end
240 244
241 245 def bulk_update
242 246 @issues.sort!
243 247 @copy = params[:copy].present?
244 248 attributes = parse_params_for_bulk_issue_attributes(params)
245 249
246 250 unsaved_issue_ids = []
247 251 moved_issues = []
248 252
249 253 if @copy && params[:copy_subtasks].present?
250 254 # Descendant issues will be copied with the parent task
251 255 # Don't copy them twice
252 256 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
253 257 end
254 258
255 259 @issues.each do |issue|
256 260 issue.reload
257 261 if @copy
258 262 issue = issue.copy({},
259 263 :attachments => params[:copy_attachments].present?,
260 264 :subtasks => params[:copy_subtasks].present?
261 265 )
262 266 end
263 267 journal = issue.init_journal(User.current, params[:notes])
264 268 issue.safe_attributes = attributes
265 269 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
266 270 if issue.save
267 271 moved_issues << issue
268 272 else
269 273 # Keep unsaved issue ids to display them in flash error
270 274 unsaved_issue_ids << issue.id
271 275 end
272 276 end
273 277 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
274 278
275 279 if params[:follow]
276 280 if @issues.size == 1 && moved_issues.size == 1
277 281 redirect_to issue_path(moved_issues.first)
278 282 elsif moved_issues.map(&:project).uniq.size == 1
279 283 redirect_to project_issues_path(moved_issues.map(&:project).first)
280 284 end
281 285 else
282 286 redirect_back_or_default _project_issues_path(@project)
283 287 end
284 288 end
285 289
286 290 def destroy
287 291 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
288 292 if @hours > 0
289 293 case params[:todo]
290 294 when 'destroy'
291 295 # nothing to do
292 296 when 'nullify'
293 297 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
294 298 when 'reassign'
295 299 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
296 300 if reassign_to.nil?
297 301 flash.now[:error] = l(:error_issue_not_found_in_project)
298 302 return
299 303 else
300 304 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
301 305 end
302 306 else
303 307 # display the destroy form if it's a user request
304 308 return unless api_request?
305 309 end
306 310 end
307 311 @issues.each do |issue|
308 312 begin
309 313 issue.reload.destroy
310 314 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
311 315 # nothing to do, issue was already deleted (eg. by a parent)
312 316 end
313 317 end
314 318 respond_to do |format|
315 319 format.html { redirect_back_or_default _project_issues_path(@project) }
316 320 format.api { render_api_ok }
317 321 end
318 322 end
319 323
320 324 private
321 325
322 326 def find_project
323 327 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
324 328 @project = Project.find(project_id)
325 329 rescue ActiveRecord::RecordNotFound
326 330 render_404
327 331 end
328 332
329 333 def retrieve_previous_and_next_issue_ids
330 334 retrieve_query_from_session
331 335 if @query
332 336 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
333 337 sort_update(@query.sortable_columns, 'issues_index_sort')
334 338 limit = 500
335 339 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
336 340 if (idx = issue_ids.index(@issue.id)) && idx < limit
337 341 if issue_ids.size < 500
338 342 @issue_position = idx + 1
339 343 @issue_count = issue_ids.size
340 344 end
341 345 @prev_issue_id = issue_ids[idx - 1] if idx > 0
342 346 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
343 347 end
344 348 end
345 349 end
346 350
347 351 # Used by #edit and #update to set some common instance variables
348 352 # from the params
349 353 # TODO: Refactor, not everything in here is needed by #edit
350 354 def update_issue_from_params
351 355 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
352 356 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
353 357 @time_entry.attributes = params[:time_entry]
354 358
355 359 @issue.init_journal(User.current)
356 360
357 361 issue_attributes = params[:issue]
358 362 if issue_attributes && params[:conflict_resolution]
359 363 case params[:conflict_resolution]
360 364 when 'overwrite'
361 365 issue_attributes = issue_attributes.dup
362 366 issue_attributes.delete(:lock_version)
363 367 when 'add_notes'
364 368 issue_attributes = issue_attributes.slice(:notes)
365 369 when 'cancel'
366 370 redirect_to issue_path(@issue)
367 371 return false
368 372 end
369 373 end
370 374 @issue.safe_attributes = issue_attributes
371 375 @priorities = IssuePriority.active
372 376 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
373 377 true
374 378 end
375 379
376 380 # TODO: Refactor, lots of extra code in here
377 381 # TODO: Changing tracker on an existing issue should not trigger this
378 382 def build_new_issue_from_params
379 383 if params[:id].blank?
380 384 @issue = Issue.new
381 385 if params[:copy_from]
382 386 begin
383 387 @copy_from = Issue.visible.find(params[:copy_from])
384 388 @copy_attachments = params[:copy_attachments].present? || request.get?
385 389 @copy_subtasks = params[:copy_subtasks].present? || request.get?
386 390 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
387 391 rescue ActiveRecord::RecordNotFound
388 392 render_404
389 393 return
390 394 end
391 395 end
392 396 @issue.project = @project
393 397 else
394 398 @issue = @project.issues.visible.find(params[:id])
395 399 end
396 400
397 401 @issue.project = @project
398 402 @issue.author ||= User.current
399 403 # Tracker must be set before custom field values
400 404 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
401 405 if @issue.tracker.nil?
402 406 render_error l(:error_no_tracker_in_project)
403 407 return false
404 408 end
405 409 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
406 410 @issue.safe_attributes = params[:issue]
407 411
408 412 @priorities = IssuePriority.active
409 413 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
410 414 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
411 415 end
412 416
413 417 def check_for_default_issue_status
414 418 if IssueStatus.default.nil?
415 419 render_error l(:error_no_default_issue_status)
416 420 return false
417 421 end
418 422 end
419 423
420 424 def parse_params_for_bulk_issue_attributes(params)
421 425 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
422 426 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
423 427 if custom = attributes[:custom_field_values]
424 428 custom.reject! {|k,v| v.blank?}
425 429 custom.keys.each do |k|
426 430 if custom[k].is_a?(Array)
427 431 custom[k] << '' if custom[k].delete('__none__')
428 432 else
429 433 custom[k] = '' if custom[k] == '__none__'
430 434 end
431 435 end
432 436 end
433 437 attributes
434 438 end
435 439 end
1 NO CONTENT: file renamed from app/views/issues/_update_form.js.erb to app/views/issues/update_form.js.erb
@@ -1,347 +1,347
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 RedmineApp::Application.routes.draw do
19 19 root :to => 'welcome#index', :as => 'home'
20 20
21 21 match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]
22 22 match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post]
23 23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
24 24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
25 25 match 'account/activate', :to => 'account#activate', :via => :get
26 26
27 27 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put]
28 28 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put]
29 29 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put]
30 30 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put]
31 31
32 32 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
33 33 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
34 34
35 35 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message'
36 36 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
37 37 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
38 38 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
39 39
40 40 post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message'
41 41 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
42 42 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
43 43 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
44 44
45 45 # Misc issue routes. TODO: move into resources
46 46 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
47 47 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
48 48 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
49 49 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
50 50
51 51 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
52 52 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
53 53
54 54 get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt'
55 55 get '/issues/gantt', :to => 'gantts#show'
56 56
57 57 get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar'
58 58 get '/issues/calendar', :to => 'calendars#show'
59 59
60 60 get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report'
61 61 get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details'
62 62
63 63 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
64 64 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
65 65 match 'my/page', :controller => 'my', :action => 'page', :via => :get
66 66 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
67 67 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
68 68 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
69 69 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
70 70 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
71 71 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
72 72 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
73 73 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
74 74
75 75 resources :users
76 76 match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership'
77 77 match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete
78 78 match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
79 79
80 80 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
81 81 delete 'watchers/watch', :to => 'watchers#unwatch'
82 82 get 'watchers/new', :to => 'watchers#new'
83 83 post 'watchers', :to => 'watchers#create'
84 84 post 'watchers/append', :to => 'watchers#append'
85 85 post 'watchers/destroy', :to => 'watchers#destroy'
86 86 get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user'
87 87 # Specific routes for issue watchers API
88 88 post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue'
89 89 delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue'
90 90
91 91 resources :projects do
92 92 member do
93 93 get 'settings(/:tab)', :action => 'settings', :as => 'settings'
94 94 post 'modules'
95 95 post 'archive'
96 96 post 'unarchive'
97 97 post 'close'
98 98 post 'reopen'
99 99 match 'copy', :via => [:get, :post]
100 100 end
101 101
102 102 resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
103 103 collection do
104 104 get 'autocomplete'
105 105 end
106 106 end
107 107
108 108 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
109 109
110 110 get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue'
111 111 resources :issues, :only => [:index, :new, :create] do
112 112 resources :time_entries, :controller => 'timelog' do
113 113 collection do
114 114 get 'report'
115 115 end
116 116 end
117 117 end
118 118 # issue form update
119 match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form'
119 match 'issues/update_form', :controller => 'issues', :action => 'update_form', :via => [:put, :post], :as => 'issue_form'
120 120
121 121 resources :files, :only => [:index, :new, :create]
122 122
123 123 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
124 124 collection do
125 125 put 'close_completed'
126 126 end
127 127 end
128 128 get 'versions.:format', :to => 'versions#index'
129 129 get 'roadmap', :to => 'versions#index', :format => false
130 130 get 'versions', :to => 'versions#index'
131 131
132 132 resources :news, :except => [:show, :edit, :update, :destroy]
133 133 resources :time_entries, :controller => 'timelog' do
134 134 get 'report', :on => :collection
135 135 end
136 136 resources :queries, :only => [:new, :create]
137 137 resources :issue_categories, :shallow => true
138 138 resources :documents, :except => [:show, :edit, :update, :destroy]
139 139 resources :boards
140 140 resources :repositories, :shallow => true, :except => [:index, :show] do
141 141 member do
142 142 match 'committers', :via => [:get, :post]
143 143 end
144 144 end
145 145
146 146 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
147 147 resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do
148 148 member do
149 149 get 'rename'
150 150 post 'rename'
151 151 get 'history'
152 152 get 'diff'
153 153 match 'preview', :via => [:post, :put]
154 154 post 'protect'
155 155 post 'add_attachment'
156 156 end
157 157 collection do
158 158 get 'export'
159 159 get 'date_index'
160 160 end
161 161 end
162 162 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
163 163 get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/}
164 164 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
165 165 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
166 166 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
167 167 end
168 168
169 169 resources :issues do
170 170 collection do
171 171 match 'bulk_edit', :via => [:get, :post]
172 172 post 'bulk_update'
173 173 end
174 174 resources :time_entries, :controller => 'timelog' do
175 175 collection do
176 176 get 'report'
177 177 end
178 178 end
179 179 resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
180 180 end
181 181 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
182 182
183 183 resources :queries, :except => [:show]
184 184
185 185 resources :news, :only => [:index, :show, :edit, :update, :destroy]
186 186 match '/news/:id/comments', :to => 'comments#create', :via => :post
187 187 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
188 188
189 189 resources :versions, :only => [:show, :edit, :update, :destroy] do
190 190 post 'status_by', :on => :member
191 191 end
192 192
193 193 resources :documents, :only => [:show, :edit, :update, :destroy] do
194 194 post 'add_attachment', :on => :member
195 195 end
196 196
197 197 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
198 198
199 199 resources :time_entries, :controller => 'timelog', :except => :destroy do
200 200 collection do
201 201 get 'report'
202 202 get 'bulk_edit'
203 203 post 'bulk_update'
204 204 end
205 205 end
206 206 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
207 207 # TODO: delete /time_entries for bulk deletion
208 208 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
209 209
210 210 get 'projects/:id/activity', :to => 'activities#index'
211 211 get 'projects/:id/activity.:format', :to => 'activities#index'
212 212 get 'activity', :to => 'activities#index'
213 213
214 214 # repositories routes
215 215 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
216 216 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
217 217
218 218 get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))',
219 219 :to => 'repositories#changes'
220 220
221 221 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
222 222 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
223 223 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
224 224 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
225 225 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
226 226 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))',
227 227 :controller => 'repositories',
228 228 :format => false,
229 229 :constraints => {
230 230 :action => /(browse|show|entry|raw|annotate|diff)/,
231 231 :rev => /[a-z0-9\.\-_]+/
232 232 }
233 233
234 234 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
235 235 get 'projects/:id/repository/graph', :to => 'repositories#graph'
236 236
237 237 get 'projects/:id/repository/changes(/*path(.:ext))',
238 238 :to => 'repositories#changes'
239 239
240 240 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
241 241 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
242 242 get 'projects/:id/repository/revision', :to => 'repositories#revision'
243 243 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
244 244 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
245 245 get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))',
246 246 :controller => 'repositories',
247 247 :format => false,
248 248 :constraints => {
249 249 :action => /(browse|show|entry|raw|annotate|diff)/,
250 250 :rev => /[a-z0-9\.\-_]+/
251 251 }
252 252 get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))',
253 253 :controller => 'repositories',
254 254 :action => /(browse|show|entry|raw|changes|annotate|diff)/
255 255 get 'projects/:id/repository/:action(/*path(.:ext))',
256 256 :controller => 'repositories',
257 257 :action => /(browse|show|entry|raw|changes|annotate|diff)/
258 258
259 259 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
260 260 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
261 261
262 262 # additional routes for having the file name at the end of url
263 263 get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment'
264 264 get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
265 265 get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
266 266 get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
267 267 resources :attachments, :only => [:show, :destroy]
268 268
269 269 resources :groups do
270 270 member do
271 271 get 'autocomplete_for_user'
272 272 end
273 273 end
274 274
275 275 match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users'
276 276 match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user'
277 277 match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post
278 278 match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post
279 279
280 280 resources :trackers, :except => :show do
281 281 collection do
282 282 match 'fields', :via => [:get, :post]
283 283 end
284 284 end
285 285 resources :issue_statuses, :except => :show do
286 286 collection do
287 287 post 'update_issue_done_ratio'
288 288 end
289 289 end
290 290 resources :custom_fields, :except => :show
291 291 resources :roles do
292 292 collection do
293 293 match 'permissions', :via => [:get, :post]
294 294 end
295 295 end
296 296 resources :enumerations, :except => :show
297 297 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
298 298
299 299 get 'projects/:id/search', :controller => 'search', :action => 'index'
300 300 get 'search', :controller => 'search', :action => 'index'
301 301
302 302 match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post
303 303
304 304 match 'admin', :controller => 'admin', :action => 'index', :via => :get
305 305 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
306 306 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
307 307 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
308 308 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
309 309 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
310 310
311 311 resources :auth_sources do
312 312 member do
313 313 get 'test_connection', :as => 'try_connection'
314 314 end
315 315 collection do
316 316 get 'autocomplete_for_new_user'
317 317 end
318 318 end
319 319
320 320 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
321 321 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
322 322 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
323 323 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
324 324 match 'settings', :controller => 'settings', :action => 'index', :via => :get
325 325 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
326 326 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings'
327 327
328 328 match 'sys/projects', :to => 'sys#projects', :via => :get
329 329 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
330 330 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get
331 331
332 332 match 'uploads', :to => 'attachments#upload', :via => :post
333 333
334 334 get 'robots.txt', :to => 'welcome#robots'
335 335
336 336 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
337 337 file = File.join(plugin_dir, "config/routes.rb")
338 338 if File.exists?(file)
339 339 begin
340 340 instance_eval File.read(file)
341 341 rescue Exception => e
342 342 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
343 343 exit 1
344 344 end
345 345 end
346 346 end
347 347 end
@@ -1,284 +1,284
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/core_ext'
19 19
20 20 begin
21 21 require 'RMagick' unless Object.const_defined?(:Magick)
22 22 rescue LoadError
23 23 # RMagick is not available
24 24 end
25 25
26 26 require 'redmine/scm/base'
27 27 require 'redmine/access_control'
28 28 require 'redmine/access_keys'
29 29 require 'redmine/activity'
30 30 require 'redmine/activity/fetcher'
31 31 require 'redmine/ciphering'
32 32 require 'redmine/codeset_util'
33 33 require 'redmine/custom_field_format'
34 34 require 'redmine/i18n'
35 35 require 'redmine/menu_manager'
36 36 require 'redmine/notifiable'
37 37 require 'redmine/platform'
38 38 require 'redmine/mime_type'
39 39 require 'redmine/notifiable'
40 40 require 'redmine/search'
41 41 require 'redmine/syntax_highlighting'
42 42 require 'redmine/thumbnail'
43 43 require 'redmine/unified_diff'
44 44 require 'redmine/utils'
45 45 require 'redmine/version'
46 46 require 'redmine/wiki_formatting'
47 47
48 48 require 'redmine/default_data/loader'
49 49 require 'redmine/helpers/calendar'
50 50 require 'redmine/helpers/diff'
51 51 require 'redmine/helpers/gantt'
52 52 require 'redmine/helpers/time_report'
53 53 require 'redmine/views/other_formats_builder'
54 54 require 'redmine/views/labelled_form_builder'
55 55 require 'redmine/views/builders'
56 56
57 57 require 'redmine/themes'
58 58 require 'redmine/hook'
59 59 require 'redmine/plugin'
60 60
61 61 if RUBY_VERSION < '1.9'
62 62 require 'fastercsv'
63 63 else
64 64 require 'csv'
65 65 FCSV = CSV
66 66 end
67 67
68 68 Redmine::Scm::Base.add "Subversion"
69 69 Redmine::Scm::Base.add "Darcs"
70 70 Redmine::Scm::Base.add "Mercurial"
71 71 Redmine::Scm::Base.add "Cvs"
72 72 Redmine::Scm::Base.add "Bazaar"
73 73 Redmine::Scm::Base.add "Git"
74 74 Redmine::Scm::Base.add "Filesystem"
75 75
76 76 Redmine::CustomFieldFormat.map do |fields|
77 77 fields.register 'string'
78 78 fields.register 'text'
79 79 fields.register 'int', :label => :label_integer
80 80 fields.register 'float'
81 81 fields.register 'list'
82 82 fields.register 'date'
83 83 fields.register 'bool', :label => :label_boolean
84 84 fields.register 'user', :only => %w(Issue TimeEntry Version Project), :edit_as => 'list'
85 85 fields.register 'version', :only => %w(Issue TimeEntry Version Project), :edit_as => 'list'
86 86 end
87 87
88 88 # Permissions
89 89 Redmine::AccessControl.map do |map|
90 90 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true
91 91 map.permission :search_project, {:search => :index}, :public => true, :read => true
92 92 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
93 93 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
94 94 map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true
95 95 map.permission :select_project_modules, {:projects => :modules}, :require => :member
96 96 map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :create, :update, :destroy, :autocomplete]}, :require => :member
97 97 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
98 98 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
99 99
100 100 map.project_module :issue_tracking do |map|
101 101 # Issue categories
102 102 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member
103 103 # Issues
104 104 map.permission :view_issues, {:issues => [:index, :show],
105 105 :auto_complete => [:issues],
106 106 :context_menus => [:issues],
107 107 :versions => [:index, :show, :status_by],
108 108 :journals => [:index, :diff],
109 109 :queries => :index,
110 110 :reports => [:issue_report, :issue_report_details]},
111 111 :read => true
112 112 map.permission :add_issues, {:issues => [:new, :create, :update_form], :attachments => :upload}
113 113 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new], :attachments => :upload}
114 114 map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
115 115 map.permission :manage_subtasks, {}
116 116 map.permission :set_issues_private, {}
117 117 map.permission :set_own_issues_private, {}, :require => :loggedin
118 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload}
118 map.permission :add_issue_notes, {:issues => [:edit, :update, :update_form], :journals => [:new], :attachments => :upload}
119 119 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
120 120 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
121 121 map.permission :view_private_notes, {}, :read => true, :require => :member
122 122 map.permission :set_notes_private, {}, :require => :member
123 123 map.permission :move_issues, {:issues => [:bulk_edit, :bulk_update]}, :require => :loggedin
124 124 map.permission :delete_issues, {:issues => :destroy}, :require => :member
125 125 # Queries
126 126 map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
127 127 map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
128 128 # Watchers
129 129 map.permission :view_issue_watchers, {}, :read => true
130 130 map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
131 131 map.permission :delete_issue_watchers, {:watchers => :destroy}
132 132 end
133 133
134 134 map.project_module :time_tracking do |map|
135 135 map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
136 136 map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true
137 137 map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
138 138 map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
139 139 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
140 140 end
141 141
142 142 map.project_module :news do |map|
143 143 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member
144 144 map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
145 145 map.permission :comment_news, {:comments => :create}
146 146 end
147 147
148 148 map.project_module :documents do |map|
149 149 map.permission :add_documents, {:documents => [:new, :create, :add_attachment]}, :require => :loggedin
150 150 map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment]}, :require => :loggedin
151 151 map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin
152 152 map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true
153 153 end
154 154
155 155 map.project_module :files do |map|
156 156 map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin
157 157 map.permission :view_files, {:files => :index, :versions => :download}, :read => true
158 158 end
159 159
160 160 map.project_module :wiki do |map|
161 161 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
162 162 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
163 163 map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
164 164 map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
165 165 map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
166 166 map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true
167 167 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment]
168 168 map.permission :delete_wiki_pages_attachments, {}
169 169 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
170 170 end
171 171
172 172 map.project_module :repository do |map|
173 173 map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member
174 174 map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true
175 175 map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true
176 176 map.permission :commit_access, {}
177 177 map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]}
178 178 end
179 179
180 180 map.project_module :boards do |map|
181 181 map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
182 182 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true
183 183 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
184 184 map.permission :edit_messages, {:messages => :edit}, :require => :member
185 185 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
186 186 map.permission :delete_messages, {:messages => :destroy}, :require => :member
187 187 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
188 188 end
189 189
190 190 map.project_module :calendar do |map|
191 191 map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true
192 192 end
193 193
194 194 map.project_module :gantt do |map|
195 195 map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true
196 196 end
197 197 end
198 198
199 199 Redmine::MenuManager.map :top_menu do |menu|
200 200 menu.push :home, :home_path
201 201 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
202 202 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
203 203 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
204 204 menu.push :help, Redmine::Info.help_url, :last => true
205 205 end
206 206
207 207 Redmine::MenuManager.map :account_menu do |menu|
208 208 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
209 209 menu.push :register, :register_path, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
210 210 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
211 211 menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? }
212 212 end
213 213
214 214 Redmine::MenuManager.map :application_menu do |menu|
215 215 # Empty
216 216 end
217 217
218 218 Redmine::MenuManager.map :admin_menu do |menu|
219 219 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
220 220 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
221 221 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
222 222 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
223 223 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
224 224 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
225 225 :html => {:class => 'issue_statuses'}
226 226 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
227 227 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
228 228 :html => {:class => 'custom_fields'}
229 229 menu.push :enumerations, {:controller => 'enumerations'}
230 230 menu.push :settings, {:controller => 'settings'}
231 231 menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
232 232 :html => {:class => 'server_authentication'}
233 233 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
234 234 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
235 235 end
236 236
237 237 Redmine::MenuManager.map :project_menu do |menu|
238 238 menu.push :overview, { :controller => 'projects', :action => 'show' }
239 239 menu.push :activity, { :controller => 'activities', :action => 'index' }
240 240 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
241 241 :if => Proc.new { |p| p.shared_versions.any? }
242 242 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
243 243 menu.push :new_issue, { :controller => 'issues', :action => 'new', :copy_from => nil }, :param => :project_id, :caption => :label_issue_new,
244 244 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
245 245 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
246 246 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
247 247 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
248 248 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
249 249 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
250 250 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
251 251 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
252 252 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
253 253 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
254 254 menu.push :repository, { :controller => 'repositories', :action => 'show', :repository_id => nil, :path => nil, :rev => nil },
255 255 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
256 256 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
257 257 end
258 258
259 259 Redmine::Activity.map do |activity|
260 260 activity.register :issues, :class_name => %w(Issue Journal)
261 261 activity.register :changesets
262 262 activity.register :news
263 263 activity.register :documents, :class_name => %w(Document Attachment)
264 264 activity.register :files, :class_name => 'Attachment'
265 265 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
266 266 activity.register :messages, :default => false
267 267 activity.register :time_entries, :default => false
268 268 end
269 269
270 270 Redmine::Search.map do |search|
271 271 search.register :issues
272 272 search.register :news
273 273 search.register :documents
274 274 search.register :changesets
275 275 search.register :wiki_pages
276 276 search.register :messages
277 277 search.register :projects
278 278 end
279 279
280 280 Redmine::WikiFormatting.map do |format|
281 281 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
282 282 end
283 283
284 284 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
@@ -1,166 +1,169
1 1 ---
2 2 users_004:
3 3 created_on: 2006-07-19 19:34:07 +02:00
4 4 status: 1
5 5 last_login_on:
6 6 language: en
7 7 # password = foo
8 8 salt: 3126f764c3c5ac61cbfc103f25f934cf
9 9 hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b
10 10 updated_on: 2006-07-19 19:34:07 +02:00
11 11 admin: false
12 12 mail: rhill@somenet.foo
13 13 lastname: Hill
14 14 firstname: Robert
15 15 id: 4
16 16 auth_source_id:
17 17 mail_notification: all
18 18 login: rhill
19 19 type: User
20 20 users_001:
21 21 created_on: 2006-07-19 19:12:21 +02:00
22 22 status: 1
23 23 last_login_on: 2006-07-19 22:57:52 +02:00
24 24 language: en
25 25 # password = admin
26 26 salt: 82090c953c4a0000a7db253b0691a6b4
27 27 hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150
28 28 updated_on: 2006-07-19 22:57:52 +02:00
29 29 admin: true
30 30 mail: admin@somenet.foo
31 31 lastname: Admin
32 32 firstname: Redmine
33 33 id: 1
34 34 auth_source_id:
35 35 mail_notification: all
36 36 login: admin
37 37 type: User
38 38 users_002:
39 39 created_on: 2006-07-19 19:32:09 +02:00
40 40 status: 1
41 41 last_login_on: 2006-07-19 22:42:15 +02:00
42 42 language: en
43 43 # password = jsmith
44 44 salt: 67eb4732624d5a7753dcea7ce0bb7d7d
45 45 hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc
46 46 updated_on: 2006-07-19 22:42:15 +02:00
47 47 admin: false
48 48 mail: jsmith@somenet.foo
49 49 lastname: Smith
50 50 firstname: John
51 51 id: 2
52 52 auth_source_id:
53 53 mail_notification: all
54 54 login: jsmith
55 55 type: User
56 56 users_003:
57 57 created_on: 2006-07-19 19:33:19 +02:00
58 58 status: 1
59 59 last_login_on:
60 60 language: en
61 61 # password = foo
62 62 salt: 7599f9963ec07b5a3b55b354407120c0
63 63 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
64 64 updated_on: 2006-07-19 19:33:19 +02:00
65 65 admin: false
66 66 mail: dlopper@somenet.foo
67 67 lastname: Lopper
68 68 firstname: Dave
69 69 id: 3
70 70 auth_source_id:
71 71 mail_notification: all
72 72 login: dlopper
73 73 type: User
74 74 users_005:
75 75 id: 5
76 76 created_on: 2006-07-19 19:33:19 +02:00
77 77 # Locked
78 78 status: 3
79 79 last_login_on:
80 80 language: en
81 81 hashed_password: 1
82 82 updated_on: 2006-07-19 19:33:19 +02:00
83 83 admin: false
84 84 mail: dlopper2@somenet.foo
85 85 lastname: Lopper2
86 86 firstname: Dave2
87 87 auth_source_id:
88 88 mail_notification: all
89 89 login: dlopper2
90 90 type: User
91 91 users_006:
92 92 id: 6
93 93 created_on: 2006-07-19 19:33:19 +02:00
94 94 status: 0
95 95 last_login_on:
96 96 language: ''
97 97 hashed_password: 1
98 98 updated_on: 2006-07-19 19:33:19 +02:00
99 99 admin: false
100 100 mail: ''
101 101 lastname: Anonymous
102 102 firstname: ''
103 103 auth_source_id:
104 104 mail_notification: only_my_events
105 105 login: ''
106 106 type: AnonymousUser
107 107 users_007:
108 # A user who does not belong to any project
108 109 id: 7
109 110 created_on: 2006-07-19 19:33:19 +02:00
110 111 status: 1
111 112 last_login_on:
112 language: ''
113 hashed_password: 1
113 language: 'en'
114 # password = foo
115 salt: 7599f9963ec07b5a3b55b354407120c0
116 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
114 117 updated_on: 2006-07-19 19:33:19 +02:00
115 118 admin: false
116 119 mail: someone@foo.bar
117 120 lastname: One
118 121 firstname: Some
119 122 auth_source_id:
120 123 mail_notification: only_my_events
121 124 login: someone
122 125 type: User
123 126 users_008:
124 127 id: 8
125 128 created_on: 2006-07-19 19:33:19 +02:00
126 129 status: 1
127 130 last_login_on:
128 131 language: 'it'
129 132 # password = foo
130 133 salt: 7599f9963ec07b5a3b55b354407120c0
131 134 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
132 135 updated_on: 2006-07-19 19:33:19 +02:00
133 136 admin: false
134 137 mail: miscuser8@foo.bar
135 138 lastname: Misc
136 139 firstname: User
137 140 auth_source_id:
138 141 mail_notification: only_my_events
139 142 login: miscuser8
140 143 type: User
141 144 users_009:
142 145 id: 9
143 146 created_on: 2006-07-19 19:33:19 +02:00
144 147 status: 1
145 148 last_login_on:
146 149 language: 'it'
147 150 hashed_password: 1
148 151 updated_on: 2006-07-19 19:33:19 +02:00
149 152 admin: false
150 153 mail: miscuser9@foo.bar
151 154 lastname: Misc
152 155 firstname: User
153 156 auth_source_id:
154 157 mail_notification: only_my_events
155 158 login: miscuser9
156 159 type: User
157 160 groups_010:
158 161 id: 10
159 162 lastname: A Team
160 163 type: Group
161 164 groups_011:
162 165 id: 11
163 166 lastname: B Team
164 167 type: Group
165 168
166 169
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,229 +1,220
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssuesTest < ActionController::IntegrationTest
21 21 fixtures :projects,
22 22 :users,
23 23 :roles,
24 24 :members,
25 25 :member_roles,
26 26 :trackers,
27 27 :projects_trackers,
28 28 :enabled_modules,
29 29 :issue_statuses,
30 30 :issues,
31 31 :enumerations,
32 32 :custom_fields,
33 33 :custom_values,
34 34 :custom_fields_trackers
35 35
36 36 # create an issue
37 37 def test_add_issue
38 38 log_user('jsmith', 'jsmith')
39 39 get 'projects/1/issues/new', :tracker_id => '1'
40 40 assert_response :success
41 41 assert_template 'issues/new'
42 42
43 43 post 'projects/1/issues', :tracker_id => "1",
44 44 :issue => { :start_date => "2006-12-26",
45 45 :priority_id => "4",
46 46 :subject => "new test issue",
47 47 :category_id => "",
48 48 :description => "new issue",
49 49 :done_ratio => "0",
50 50 :due_date => "",
51 51 :assigned_to_id => "" },
52 52 :custom_fields => {'2' => 'Value for field 2'}
53 53 # find created issue
54 54 issue = Issue.find_by_subject("new test issue")
55 55 assert_kind_of Issue, issue
56 56
57 57 # check redirection
58 58 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
59 59 follow_redirect!
60 60 assert_equal issue, assigns(:issue)
61 61
62 62 # check issue attributes
63 63 assert_equal 'jsmith', issue.author.login
64 64 assert_equal 1, issue.project.id
65 65 assert_equal 1, issue.status.id
66 66 end
67 67
68 def test_update_issue_form
69 log_user('jsmith', 'jsmith')
70 post 'projects/ecookbook/issues/new', :issue => { :tracker_id => "2"}
71 assert_response :success
72 assert_tag 'select',
73 :attributes => {:name => 'issue[tracker_id]'},
74 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}}
75 end
76
77 68 # add then remove 2 attachments to an issue
78 69 def test_issue_attachments
79 70 log_user('jsmith', 'jsmith')
80 71 set_tmp_attachments_directory
81 72
82 73 put 'issues/1',
83 74 :notes => 'Some notes',
84 75 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
85 76 assert_redirected_to "/issues/1"
86 77
87 78 # make sure attachment was saved
88 79 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
89 80 assert_kind_of Attachment, attachment
90 81 assert_equal Issue.find(1), attachment.container
91 82 assert_equal 'This is an attachment', attachment.description
92 83 # verify the size of the attachment stored in db
93 84 #assert_equal file_data_1.length, attachment.filesize
94 85 # verify that the attachment was written to disk
95 86 assert File.exist?(attachment.diskfile)
96 87
97 88 # remove the attachments
98 89 Issue.find(1).attachments.each(&:destroy)
99 90 assert_equal 0, Issue.find(1).attachments.length
100 91 end
101 92
102 93 def test_other_formats_links_on_index
103 94 get '/projects/ecookbook/issues'
104 95
105 96 %w(Atom PDF CSV).each do |format|
106 97 assert_tag :a, :content => format,
107 98 :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}",
108 99 :rel => 'nofollow' }
109 100 end
110 101 end
111 102
112 103 def test_other_formats_links_on_index_without_project_id_in_url
113 104 get '/issues', :project_id => 'ecookbook'
114 105
115 106 %w(Atom PDF CSV).each do |format|
116 107 assert_tag :a, :content => format,
117 108 :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}",
118 109 :rel => 'nofollow' }
119 110 end
120 111 end
121 112
122 113 def test_pagination_links_on_index
123 114 Setting.per_page_options = '2'
124 115 get '/projects/ecookbook/issues'
125 116
126 117 assert_tag :a, :content => '2',
127 118 :attributes => { :href => '/projects/ecookbook/issues?page=2' }
128 119
129 120 end
130 121
131 122 def test_pagination_links_on_index_without_project_id_in_url
132 123 Setting.per_page_options = '2'
133 124 get '/issues', :project_id => 'ecookbook'
134 125
135 126 assert_tag :a, :content => '2',
136 127 :attributes => { :href => '/projects/ecookbook/issues?page=2' }
137 128
138 129 end
139 130
140 131 def test_issue_with_user_custom_field
141 132 @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all)
142 133 Role.anonymous.add_permission! :add_issues, :edit_issues
143 134 users = Project.find(1).users
144 135 tester = users.first
145 136
146 137 # Issue form
147 138 get '/projects/ecookbook/issues/new'
148 139 assert_response :success
149 140 assert_tag :select,
150 141 :attributes => {:name => "issue[custom_field_values][#{@field.id}]"},
151 142 :children => {:count => (users.size + 1)}, # +1 for blank value
152 143 :child => {
153 144 :tag => 'option',
154 145 :attributes => {:value => tester.id.to_s},
155 146 :content => tester.name
156 147 }
157 148
158 149 # Create issue
159 150 assert_difference 'Issue.count' do
160 151 post '/projects/ecookbook/issues',
161 152 :issue => {
162 153 :tracker_id => '1',
163 154 :priority_id => '4',
164 155 :subject => 'Issue with user custom field',
165 156 :custom_field_values => {@field.id.to_s => users.first.id.to_s}
166 157 }
167 158 end
168 159 issue = Issue.first(:order => 'id DESC')
169 160 assert_response 302
170 161
171 162 # Issue view
172 163 follow_redirect!
173 164 assert_tag :th,
174 165 :content => /Tester/,
175 166 :sibling => {
176 167 :tag => 'td',
177 168 :content => tester.name
178 169 }
179 170 assert_tag :select,
180 171 :attributes => {:name => "issue[custom_field_values][#{@field.id}]"},
181 172 :children => {:count => (users.size + 1)}, # +1 for blank value
182 173 :child => {
183 174 :tag => 'option',
184 175 :attributes => {:value => tester.id.to_s, :selected => 'selected'},
185 176 :content => tester.name
186 177 }
187 178
188 179 # Update issue
189 180 new_tester = users[1]
190 181 assert_difference 'Journal.count' do
191 182 put "/issues/#{issue.id}",
192 183 :notes => 'Updating custom field',
193 184 :issue => {
194 185 :custom_field_values => {@field.id.to_s => new_tester.id.to_s}
195 186 }
196 187 end
197 188 assert_response 302
198 189
199 190 # Issue view
200 191 follow_redirect!
201 192 assert_tag :content => 'Tester',
202 193 :ancestor => {:tag => 'ul', :attributes => {:class => /details/}},
203 194 :sibling => {
204 195 :content => tester.name,
205 196 :sibling => {
206 197 :content => new_tester.name
207 198 }
208 199 }
209 200 end
210 201
211 202 def test_update_using_invalid_http_verbs
212 203 subject = 'Updated by an invalid http verb'
213 204
214 205 get '/issues/update/1', {:issue => {:subject => subject}}, credentials('jsmith')
215 206 assert_response 404
216 207 assert_not_equal subject, Issue.find(1).subject
217 208
218 209 post '/issues/1', {:issue => {:subject => subject}}, credentials('jsmith')
219 210 assert_response 404
220 211 assert_not_equal subject, Issue.find(1).subject
221 212 end
222 213
223 214 def test_get_watch_should_be_invalid
224 215 assert_no_difference 'Watcher.count' do
225 216 get '/watchers/watch?object_type=issue&object_id=1', {}, credentials('jsmith')
226 217 assert_response 404
227 218 end
228 219 end
229 220 end
@@ -1,134 +1,134
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class RoutingIssuesTest < ActionController::IntegrationTest
21 21 def test_issues_rest_actions
22 22 assert_routing(
23 23 { :method => 'get', :path => "/issues" },
24 24 { :controller => 'issues', :action => 'index' }
25 25 )
26 26 assert_routing(
27 27 { :method => 'get', :path => "/issues.pdf" },
28 28 { :controller => 'issues', :action => 'index', :format => 'pdf' }
29 29 )
30 30 assert_routing(
31 31 { :method => 'get', :path => "/issues.atom" },
32 32 { :controller => 'issues', :action => 'index', :format => 'atom' }
33 33 )
34 34 assert_routing(
35 35 { :method => 'get', :path => "/issues.xml" },
36 36 { :controller => 'issues', :action => 'index', :format => 'xml' }
37 37 )
38 38 assert_routing(
39 39 { :method => 'get', :path => "/issues/64" },
40 40 { :controller => 'issues', :action => 'show', :id => '64' }
41 41 )
42 42 assert_routing(
43 43 { :method => 'get', :path => "/issues/64.pdf" },
44 44 { :controller => 'issues', :action => 'show', :id => '64',
45 45 :format => 'pdf' }
46 46 )
47 47 assert_routing(
48 48 { :method => 'get', :path => "/issues/64.atom" },
49 49 { :controller => 'issues', :action => 'show', :id => '64',
50 50 :format => 'atom' }
51 51 )
52 52 assert_routing(
53 53 { :method => 'get', :path => "/issues/64.xml" },
54 54 { :controller => 'issues', :action => 'show', :id => '64',
55 55 :format => 'xml' }
56 56 )
57 57 assert_routing(
58 58 { :method => 'post', :path => "/issues.xml" },
59 59 { :controller => 'issues', :action => 'create', :format => 'xml' }
60 60 )
61 61 assert_routing(
62 62 { :method => 'get', :path => "/issues/64/edit" },
63 63 { :controller => 'issues', :action => 'edit', :id => '64' }
64 64 )
65 65 assert_routing(
66 66 { :method => 'put', :path => "/issues/1.xml" },
67 67 { :controller => 'issues', :action => 'update', :id => '1',
68 68 :format => 'xml' }
69 69 )
70 70 assert_routing(
71 71 { :method => 'delete', :path => "/issues/1.xml" },
72 72 { :controller => 'issues', :action => 'destroy', :id => '1',
73 73 :format => 'xml' }
74 74 )
75 75 end
76 76
77 77 def test_issues_rest_actions_scoped_under_project
78 78 assert_routing(
79 79 { :method => 'get', :path => "/projects/23/issues" },
80 80 { :controller => 'issues', :action => 'index', :project_id => '23' }
81 81 )
82 82 assert_routing(
83 83 { :method => 'get', :path => "/projects/23/issues.pdf" },
84 84 { :controller => 'issues', :action => 'index', :project_id => '23',
85 85 :format => 'pdf' }
86 86 )
87 87 assert_routing(
88 88 { :method => 'get', :path => "/projects/23/issues.atom" },
89 89 { :controller => 'issues', :action => 'index', :project_id => '23',
90 90 :format => 'atom' }
91 91 )
92 92 assert_routing(
93 93 { :method => 'get', :path => "/projects/23/issues.xml" },
94 94 { :controller => 'issues', :action => 'index', :project_id => '23',
95 95 :format => 'xml' }
96 96 )
97 97 assert_routing(
98 98 { :method => 'post', :path => "/projects/23/issues" },
99 99 { :controller => 'issues', :action => 'create', :project_id => '23' }
100 100 )
101 101 assert_routing(
102 102 { :method => 'get', :path => "/projects/23/issues/new" },
103 103 { :controller => 'issues', :action => 'new', :project_id => '23' }
104 104 )
105 105 end
106 106
107 107 def test_issues_form_update
108 108 ["post", "put"].each do |method|
109 109 assert_routing(
110 { :method => method, :path => "/projects/23/issues/new" },
111 { :controller => 'issues', :action => 'new', :project_id => '23' }
110 { :method => method, :path => "/projects/23/issues/update_form" },
111 { :controller => 'issues', :action => 'update_form', :project_id => '23' }
112 112 )
113 113 end
114 114 end
115 115
116 116 def test_issues_extra_actions
117 117 assert_routing(
118 118 { :method => 'get', :path => "/projects/23/issues/64/copy" },
119 119 { :controller => 'issues', :action => 'new', :project_id => '23',
120 120 :copy_from => '64' }
121 121 )
122 122 # For updating the bulk edit form
123 123 ["get", "post"].each do |method|
124 124 assert_routing(
125 125 { :method => method, :path => "/issues/bulk_edit" },
126 126 { :controller => 'issues', :action => 'bulk_edit' }
127 127 )
128 128 end
129 129 assert_routing(
130 130 { :method => 'post', :path => "/issues/bulk_update" },
131 131 { :controller => 'issues', :action => 'bulk_update' }
132 132 )
133 133 end
134 134 end
@@ -1,128 +1,186
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../base', __FILE__)
19 19
20 20 class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base
21 21 fixtures :projects, :users, :roles, :members, :member_roles,
22 22 :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
23 23 :enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
24 24 :watchers
25 25
26 26 def test_create_issue
27 27 log_user('jsmith', 'jsmith')
28 28 visit '/projects/ecookbook/issues/new'
29 29 within('form#issue-form') do
30 30 select 'Bug', :from => 'Tracker'
31 31 select 'Low', :from => 'Priority'
32 32 fill_in 'Subject', :with => 'new test issue'
33 33 fill_in 'Description', :with => 'new issue'
34 34 select '0 %', :from => 'Done'
35 35 fill_in 'Due date', :with => ''
36 36 select '', :from => 'Assignee'
37 37 fill_in 'Searchable field', :with => 'Value for field 2'
38 38 # click_button 'Create' would match both 'Create' and 'Create and continue' buttons
39 39 find('input[name=commit]').click
40 40 end
41 41
42 42 # find created issue
43 43 issue = Issue.find_by_subject("new test issue")
44 44 assert_kind_of Issue, issue
45 45
46 46 # check redirection
47 47 find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created."
48 48 assert_equal issue_path(:id => issue), current_path
49 49
50 50 # check issue attributes
51 51 assert_equal 'jsmith', issue.author.login
52 52 assert_equal 1, issue.project.id
53 53 assert_equal IssueStatus.find_by_name('New'), issue.status
54 54 assert_equal Tracker.find_by_name('Bug'), issue.tracker
55 55 assert_equal IssuePriority.find_by_name('Low'), issue.priority
56 56 assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field'))
57 57 end
58 58
59 def test_create_issue_with_form_update
60 field = IssueCustomField.create!(
61 :field_format => 'string',
62 :name => 'Form update CF',
63 :is_for_all => true,
64 :trackers => Tracker.find_all_by_name('Feature request')
65 )
66
67 Role.non_member.add_permission! :add_issues
68 Role.non_member.remove_permission! :edit_issues, :add_issue_notes
69
70 log_user('someone', 'foo')
71 visit '/projects/ecookbook/issues/new'
72 assert page.has_no_content?('Form update CF')
73
74 fill_in 'Subject', :with => 'new test issue'
75 # the custom field should show up when changing tracker
76 select 'Feature request', :from => 'Tracker'
77 assert page.has_content?('Form update CF')
78
79 fill_in 'Form update', :with => 'CF value'
80 assert_difference 'Issue.count' do
81 find('input[name=commit]').click
82 end
83
84 issue = Issue.order('id desc').first
85 assert_equal 'CF value', issue.custom_field_value(field)
86 end
87
59 88 def test_create_issue_with_watchers
60 89 User.generate!(:firstname => 'Some', :lastname => 'Watcher')
61 90
62 91 log_user('jsmith', 'jsmith')
63 92 visit '/projects/ecookbook/issues/new'
64 93 fill_in 'Subject', :with => 'Issue with watchers'
65 94 # Add a project member as watcher
66 95 check 'Dave Lopper'
67 96 # Search for another user
68 97 click_link 'Search for watchers to add'
69 98 within('form#new-watcher-form') do
70 99 assert page.has_content?('Some One')
71 100 fill_in 'user_search', :with => 'watch'
72 101 assert page.has_no_content?('Some One')
73 102 check 'Some Watcher'
74 103 click_button 'Add'
75 104 end
76 105 assert_difference 'Issue.count' do
77 106 find('input[name=commit]').click
78 107 end
79 108
80 109 issue = Issue.order('id desc').first
81 110 assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort
82 111 end
83 112
84 113 def test_preview_issue_description
85 114 log_user('jsmith', 'jsmith')
86 115 visit '/projects/ecookbook/issues/new'
87 116 within('form#issue-form') do
88 117 fill_in 'Subject', :with => 'new issue subject'
89 118 fill_in 'Description', :with => 'new issue description'
90 119 click_link 'Preview'
91 120 end
92 121 find 'div#preview fieldset', :visible => true, :text => 'new issue description'
93 122 assert_difference 'Issue.count' do
94 123 find('input[name=commit]').click
95 124 end
96 125
97 126 issue = Issue.order('id desc').first
98 127 assert_equal 'new issue description', issue.description
99 128 end
100 129
130 def test_update_issue_with_form_update
131 field = IssueCustomField.create!(
132 :field_format => 'string',
133 :name => 'Form update CF',
134 :is_for_all => true,
135 :trackers => Tracker.find_all_by_name('Feature request')
136 )
137
138 Role.non_member.add_permission! :edit_issues
139 Role.non_member.remove_permission! :add_issues, :add_issue_notes
140
141 log_user('someone', 'foo')
142 visit '/issues/1'
143 assert page.has_no_content?('Form update CF')
144
145 page.first(:link, 'Update').click
146 # the custom field should show up when changing tracker
147 select 'Feature request', :from => 'Tracker'
148 assert page.has_content?('Form update CF')
149
150 fill_in 'Form update', :with => 'CF value'
151 assert_no_difference 'Issue.count' do
152 page.first(:button, 'Submit').click
153 end
154
155 issue = Issue.find(1)
156 assert_equal 'CF value', issue.custom_field_value(field)
157 end
158
101 159 def test_watch_issue_via_context_menu
102 160 log_user('jsmith', 'jsmith')
103 161 visit '/issues'
104 162 find('tr#issue-1 td.updated_on').click
105 163 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
106 164 assert_difference 'Watcher.count' do
107 165 within('#context-menu') do
108 166 click_link 'Watch'
109 167 end
110 168 end
111 169 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
112 170 end
113 171
114 172 def test_bulk_watch_issues_via_context_menu
115 173 log_user('jsmith', 'jsmith')
116 174 visit '/issues'
117 175 find('tr#issue-1 input[type=checkbox]').click
118 176 find('tr#issue-4 input[type=checkbox]').click
119 177 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
120 178 assert_difference 'Watcher.count', 2 do
121 179 within('#context-menu') do
122 180 click_link 'Watch'
123 181 end
124 182 end
125 183 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
126 184 assert Issue.find(4).watched_by?(User.find_by_login('jsmith'))
127 185 end
128 186 end
General Comments 0
You need to be logged in to leave comments. Login now