##// END OF EJS Templates
ProjectsController#add_issue moved to IssuesController#new....
Jean-Philippe Lang -
r1066:44ac1a0debc8
parent child
Show More
@@ -1,237 +1,295
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 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :find_project, :authorize, :except => [:index, :changes, :preview]
20 before_filter :find_issue, :except => [:index, :changes, :preview, :new, :update_form]
21 before_filter :find_project, :only => [:new, :update_form]
22 before_filter :authorize, :except => [:index, :changes, :preview, :update_form]
21 before_filter :find_optional_project, :only => [:index, :changes]
23 before_filter :find_optional_project, :only => [:index, :changes]
22 accept_key_auth :index, :changes
24 accept_key_auth :index, :changes
23
25
24 cache_sweeper :issue_sweeper, :only => [ :edit, :update, :destroy ]
26 cache_sweeper :issue_sweeper, :only => [ :new, :edit, :update, :destroy ]
25
27
26 helper :projects
28 helper :projects
27 include ProjectsHelper
29 include ProjectsHelper
28 helper :custom_fields
30 helper :custom_fields
29 include CustomFieldsHelper
31 include CustomFieldsHelper
30 helper :ifpdf
32 helper :ifpdf
31 include IfpdfHelper
33 include IfpdfHelper
32 helper :issue_relations
34 helper :issue_relations
33 include IssueRelationsHelper
35 include IssueRelationsHelper
34 helper :watchers
36 helper :watchers
35 include WatchersHelper
37 include WatchersHelper
36 helper :attachments
38 helper :attachments
37 include AttachmentsHelper
39 include AttachmentsHelper
38 helper :queries
40 helper :queries
39 helper :sort
41 helper :sort
40 include SortHelper
42 include SortHelper
41 include IssuesHelper
43 include IssuesHelper
42
44
43 def index
45 def index
44 sort_init "#{Issue.table_name}.id", "desc"
46 sort_init "#{Issue.table_name}.id", "desc"
45 sort_update
47 sort_update
46 retrieve_query
48 retrieve_query
47 if @query.valid?
49 if @query.valid?
48 limit = %w(pdf csv).include?(params[:format]) ? Setting.issues_export_limit.to_i : per_page_option
50 limit = %w(pdf csv).include?(params[:format]) ? Setting.issues_export_limit.to_i : per_page_option
49 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
51 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
50 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
52 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
51 @issues = Issue.find :all, :order => sort_clause,
53 @issues = Issue.find :all, :order => sort_clause,
52 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
54 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
53 :conditions => @query.statement,
55 :conditions => @query.statement,
54 :limit => limit,
56 :limit => limit,
55 :offset => @issue_pages.current.offset
57 :offset => @issue_pages.current.offset
56 respond_to do |format|
58 respond_to do |format|
57 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
59 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
58 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
60 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
59 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
61 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
60 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
62 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
61 end
63 end
62 else
64 else
63 # Send html if the query is not valid
65 # Send html if the query is not valid
64 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
66 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
65 end
67 end
66 end
68 end
67
69
68 def changes
70 def changes
69 sort_init "#{Issue.table_name}.id", "desc"
71 sort_init "#{Issue.table_name}.id", "desc"
70 sort_update
72 sort_update
71 retrieve_query
73 retrieve_query
72 if @query.valid?
74 if @query.valid?
73 @changes = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
75 @changes = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
74 :conditions => @query.statement,
76 :conditions => @query.statement,
75 :limit => 25,
77 :limit => 25,
76 :order => "#{Journal.table_name}.created_on DESC"
78 :order => "#{Journal.table_name}.created_on DESC"
77 end
79 end
78 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
80 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
79 render :layout => false, :content_type => 'application/atom+xml'
81 render :layout => false, :content_type => 'application/atom+xml'
80 end
82 end
81
83
82 def show
84 def show
83 @custom_values = @issue.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
85 @custom_values = @issue.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
84 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
86 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
85 @status_options = @issue.new_statuses_allowed_to(User.current)
87 @status_options = @issue.new_statuses_allowed_to(User.current)
86 @activities = Enumeration::get_values('ACTI')
88 @activities = Enumeration::get_values('ACTI')
87 respond_to do |format|
89 respond_to do |format|
88 format.html { render :template => 'issues/show.rhtml' }
90 format.html { render :template => 'issues/show.rhtml' }
89 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
91 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
90 end
92 end
91 end
93 end
92
94
95 # Add a new issue
96 # The new issue will be created from an existing one if copy_from parameter is given
97 def new
98 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
99 @issue.project = @project
100 @issue.author = User.current
101 @issue.tracker ||= @project.trackers.find(params[:tracker_id] ? params[:tracker_id] : :first)
102 if @issue.tracker.nil?
103 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
104 render :nothing => true, :layout => true
105 return
106 end
107
108 default_status = IssueStatus.default
109 unless default_status
110 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
111 render :nothing => true, :layout => true
112 return
113 end
114 @issue.status = default_status
115 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker))
116
117 if request.get? || request.xhr?
118 @issue.start_date ||= Date.today
119 @custom_values = @issue.custom_values.empty? ?
120 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
121 @issue.custom_values
122 else
123 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
124 # Check that the user is allowed to apply the requested status
125 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
126 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
127 @issue.custom_values = @custom_values
128 if @issue.save
129 attach_files(@issue, params[:attachments])
130 flash[:notice] = l(:notice_successful_create)
131 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
132 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
133 return
134 end
135 end
136 @priorities = Enumeration::get_values('IPRI')
137 render :layout => !request.xhr?
138 end
139
93 def edit
140 def edit
94 @priorities = Enumeration::get_values('IPRI')
141 @priorities = Enumeration::get_values('IPRI')
95 @custom_values = []
142 @custom_values = []
96 if request.get?
143 if request.get?
97 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
144 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
98 else
145 else
99 begin
146 begin
100 journal = @issue.init_journal(User.current)
147 journal = @issue.init_journal(User.current)
101 # Retrieve custom fields and values
148 # Retrieve custom fields and values
102 if params["custom_fields"]
149 if params["custom_fields"]
103 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
150 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
104 @issue.custom_values = @custom_values
151 @issue.custom_values = @custom_values
105 end
152 end
106 @issue.attributes = params[:issue]
153 @issue.attributes = params[:issue]
107 if @issue.save
154 if @issue.save
108 flash[:notice] = l(:notice_successful_update)
155 flash[:notice] = l(:notice_successful_update)
109 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
156 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
110 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
157 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
111 end
158 end
112 rescue ActiveRecord::StaleObjectError
159 rescue ActiveRecord::StaleObjectError
113 # Optimistic locking exception
160 # Optimistic locking exception
114 flash[:error] = l(:notice_locking_conflict)
161 flash[:error] = l(:notice_locking_conflict)
115 end
162 end
116 end
163 end
117 end
164 end
118
165
119 # Attributes that can be updated on workflow transition
166 # Attributes that can be updated on workflow transition
120 # TODO: make it configurable (at least per role)
167 # TODO: make it configurable (at least per role)
121 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
168 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
122
169
123 def update
170 def update
124 @status_options = @issue.new_statuses_allowed_to(User.current)
171 @status_options = @issue.new_statuses_allowed_to(User.current)
125 @activities = Enumeration::get_values('ACTI')
172 @activities = Enumeration::get_values('ACTI')
126 journal = @issue.init_journal(User.current, params[:notes])
173 journal = @issue.init_journal(User.current, params[:notes])
127 # User can change issue attributes only if a workflow transition is allowed
174 # User can change issue attributes only if a workflow transition is allowed
128 if !@status_options.empty? && params[:issue]
175 if !@status_options.empty? && params[:issue]
129 attrs = params[:issue].dup
176 attrs = params[:issue].dup
130 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) }
177 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) }
131 attrs.delete(:status_id) unless @status_options.detect {|s| s.id.to_s == attrs[:status_id].to_s}
178 attrs.delete(:status_id) unless @status_options.detect {|s| s.id.to_s == attrs[:status_id].to_s}
132 @issue.attributes = attrs
179 @issue.attributes = attrs
133 end
180 end
134 if request.post?
181 if request.post?
135 attachments = attach_files(@issue, params[:attachments])
182 attachments = attach_files(@issue, params[:attachments])
136 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
183 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
137 if @issue.save
184 if @issue.save
138 # Log spend time
185 # Log spend time
139 if current_role.allowed_to?(:log_time)
186 if current_role.allowed_to?(:log_time)
140 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
187 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
141 @time_entry.attributes = params[:time_entry]
188 @time_entry.attributes = params[:time_entry]
142 @time_entry.save
189 @time_entry.save
143 end
190 end
144 if !journal.new_record?
191 if !journal.new_record?
145 # Only send notification if something was actually changed
192 # Only send notification if something was actually changed
146 flash[:notice] = l(:notice_successful_update)
193 flash[:notice] = l(:notice_successful_update)
147 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
194 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
148 end
195 end
149 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
196 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
150 end
197 end
151 end
198 end
152 rescue ActiveRecord::StaleObjectError
199 rescue ActiveRecord::StaleObjectError
153 # Optimistic locking exception
200 # Optimistic locking exception
154 flash.now[:error] = l(:notice_locking_conflict)
201 flash.now[:error] = l(:notice_locking_conflict)
155 end
202 end
156
203
157 def destroy
204 def destroy
158 @issue.destroy
205 @issue.destroy
159 redirect_to :action => 'index', :project_id => @project
206 redirect_to :action => 'index', :project_id => @project
160 end
207 end
161
208
162 def destroy_attachment
209 def destroy_attachment
163 a = @issue.attachments.find(params[:attachment_id])
210 a = @issue.attachments.find(params[:attachment_id])
164 a.destroy
211 a.destroy
165 journal = @issue.init_journal(User.current)
212 journal = @issue.init_journal(User.current)
166 journal.details << JournalDetail.new(:property => 'attachment',
213 journal.details << JournalDetail.new(:property => 'attachment',
167 :prop_key => a.id,
214 :prop_key => a.id,
168 :old_value => a.filename)
215 :old_value => a.filename)
169 journal.save
216 journal.save
170 redirect_to :action => 'show', :id => @issue
217 redirect_to :action => 'show', :id => @issue
171 end
218 end
172
219
173 def context_menu
220 def context_menu
174 @priorities = Enumeration.get_values('IPRI').reverse
221 @priorities = Enumeration.get_values('IPRI').reverse
175 @statuses = IssueStatus.find(:all, :order => 'position')
222 @statuses = IssueStatus.find(:all, :order => 'position')
176 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
223 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
177 @assignables = @issue.assignable_users
224 @assignables = @issue.assignable_users
178 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
225 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
179 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
226 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
180 :assign => (@allowed_statuses.any? || User.current.allowed_to?(:edit_issues, @project)),
227 :assign => (@allowed_statuses.any? || User.current.allowed_to?(:edit_issues, @project)),
181 :add => User.current.allowed_to?(:add_issues, @project),
228 :add => User.current.allowed_to?(:add_issues, @project),
182 :move => User.current.allowed_to?(:move_issues, @project),
229 :move => User.current.allowed_to?(:move_issues, @project),
183 :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
230 :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
184 :delete => User.current.allowed_to?(:delete_issues, @project)}
231 :delete => User.current.allowed_to?(:delete_issues, @project)}
185 render :layout => false
232 render :layout => false
186 end
233 end
187
234
235 def update_form
236 @issue = Issue.new(params[:issue])
237 render :action => :new, :layout => false
238 end
239
188 def preview
240 def preview
189 issue = Issue.find_by_id(params[:id])
241 issue = Issue.find_by_id(params[:id])
190 @attachements = issue.attachments if issue
242 @attachements = issue.attachments if issue
191 @text = params[:issue][:description]
243 @text = params[:issue][:description]
192 render :partial => 'common/preview'
244 render :partial => 'common/preview'
193 end
245 end
194
246
195 private
247 private
196 def find_project
248 def find_issue
197 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
249 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
198 @project = @issue.project
250 @project = @issue.project
199 rescue ActiveRecord::RecordNotFound
251 rescue ActiveRecord::RecordNotFound
200 render_404
252 render_404
201 end
253 end
202
254
255 def find_project
256 @project = Project.find(params[:project_id])
257 rescue ActiveRecord::RecordNotFound
258 render_404
259 end
260
203 def find_optional_project
261 def find_optional_project
204 return true unless params[:project_id]
262 return true unless params[:project_id]
205 @project = Project.find(params[:project_id])
263 @project = Project.find(params[:project_id])
206 authorize
264 authorize
207 rescue ActiveRecord::RecordNotFound
265 rescue ActiveRecord::RecordNotFound
208 render_404
266 render_404
209 end
267 end
210
268
211 # Retrieve query from session or build a new query
269 # Retrieve query from session or build a new query
212 def retrieve_query
270 def retrieve_query
213 if params[:query_id]
271 if params[:query_id]
214 @query = Query.find(params[:query_id], :conditions => {:project_id => (@project ? @project.id : nil)})
272 @query = Query.find(params[:query_id], :conditions => {:project_id => (@project ? @project.id : nil)})
215 session[:query] = {:id => @query.id, :project_id => @query.project_id}
273 session[:query] = {:id => @query.id, :project_id => @query.project_id}
216 else
274 else
217 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
275 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
218 # Give it a name, required to be valid
276 # Give it a name, required to be valid
219 @query = Query.new(:name => "_")
277 @query = Query.new(:name => "_")
220 @query.project = @project
278 @query.project = @project
221 if params[:fields] and params[:fields].is_a? Array
279 if params[:fields] and params[:fields].is_a? Array
222 params[:fields].each do |field|
280 params[:fields].each do |field|
223 @query.add_filter(field, params[:operators][field], params[:values][field])
281 @query.add_filter(field, params[:operators][field], params[:values][field])
224 end
282 end
225 else
283 else
226 @query.available_filters.keys.each do |field|
284 @query.available_filters.keys.each do |field|
227 @query.add_short_filter(field, params[field]) if params[field]
285 @query.add_short_filter(field, params[field]) if params[field]
228 end
286 end
229 end
287 end
230 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
288 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
231 else
289 else
232 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
290 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
233 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
291 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
234 end
292 end
235 end
293 end
236 end
294 end
237 end
295 end
@@ -1,525 +1,485
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 class ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
19 layout 'base'
19 layout 'base'
20 menu_item :overview
20 menu_item :overview
21 menu_item :activity, :only => :activity
21 menu_item :activity, :only => :activity
22 menu_item :roadmap, :only => :roadmap
22 menu_item :roadmap, :only => :roadmap
23 menu_item :files, :only => [:list_files, :add_file]
23 menu_item :files, :only => [:list_files, :add_file]
24 menu_item :settings, :only => :settings
24 menu_item :settings, :only => :settings
25 menu_item :issues, :only => [:add_issue, :bulk_edit_issues, :changelog, :move_issues]
25 menu_item :issues, :only => [:bulk_edit_issues, :changelog, :move_issues]
26
26
27 before_filter :find_project, :except => [ :index, :list, :add ]
27 before_filter :find_project, :except => [ :index, :list, :add ]
28 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
28 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
29 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
29 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
30 accept_key_auth :activity, :calendar
30 accept_key_auth :activity, :calendar
31
31
32 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
32 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
33 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
34 cache_sweeper :version_sweeper, :only => [ :add_version ]
33 cache_sweeper :version_sweeper, :only => [ :add_version ]
35
34
36 helper :sort
35 helper :sort
37 include SortHelper
36 include SortHelper
38 helper :custom_fields
37 helper :custom_fields
39 include CustomFieldsHelper
38 include CustomFieldsHelper
40 helper :ifpdf
39 helper :ifpdf
41 include IfpdfHelper
40 include IfpdfHelper
42 helper :issues
41 helper :issues
43 helper IssuesHelper
42 helper IssuesHelper
44 helper :queries
43 helper :queries
45 include QueriesHelper
44 include QueriesHelper
46 helper :repositories
45 helper :repositories
47 include RepositoriesHelper
46 include RepositoriesHelper
48 include ProjectsHelper
47 include ProjectsHelper
49
48
50 def index
49 def index
51 list
50 list
52 render :action => 'list' unless request.xhr?
51 render :action => 'list' unless request.xhr?
53 end
52 end
54
53
55 # Lists visible projects
54 # Lists visible projects
56 def list
55 def list
57 projects = Project.find :all,
56 projects = Project.find :all,
58 :conditions => Project.visible_by(User.current),
57 :conditions => Project.visible_by(User.current),
59 :include => :parent
58 :include => :parent
60 @project_tree = projects.group_by {|p| p.parent || p}
59 @project_tree = projects.group_by {|p| p.parent || p}
61 @project_tree.each_key {|p| @project_tree[p] -= [p]}
60 @project_tree.each_key {|p| @project_tree[p] -= [p]}
62 end
61 end
63
62
64 # Add a new project
63 # Add a new project
65 def add
64 def add
66 @custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
65 @custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 @trackers = Tracker.all
66 @trackers = Tracker.all
68 @root_projects = Project.find(:all,
67 @root_projects = Project.find(:all,
69 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
68 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
70 :order => 'name')
69 :order => 'name')
71 @project = Project.new(params[:project])
70 @project = Project.new(params[:project])
72 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
71 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
73 if request.get?
72 if request.get?
74 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
73 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
75 @project.trackers = Tracker.all
74 @project.trackers = Tracker.all
76 else
75 else
77 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
76 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
78 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
77 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
79 @project.custom_values = @custom_values
78 @project.custom_values = @custom_values
80 if @project.save
79 if @project.save
81 @project.enabled_module_names = params[:enabled_modules]
80 @project.enabled_module_names = params[:enabled_modules]
82 flash[:notice] = l(:notice_successful_create)
81 flash[:notice] = l(:notice_successful_create)
83 redirect_to :controller => 'admin', :action => 'projects'
82 redirect_to :controller => 'admin', :action => 'projects'
84 end
83 end
85 end
84 end
86 end
85 end
87
86
88 # Show @project
87 # Show @project
89 def show
88 def show
90 @custom_values = @project.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
89 @custom_values = @project.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
91 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
90 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
92 @subprojects = @project.active_children
91 @subprojects = @project.active_children
93 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
92 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
94 @trackers = @project.trackers
93 @trackers = @project.trackers
95 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
94 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
96 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
95 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
97 @total_hours = @project.time_entries.sum(:hours)
96 @total_hours = @project.time_entries.sum(:hours)
98 @key = User.current.rss_key
97 @key = User.current.rss_key
99 end
98 end
100
99
101 def settings
100 def settings
102 @root_projects = Project.find(:all,
101 @root_projects = Project.find(:all,
103 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
102 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
104 :order => 'name')
103 :order => 'name')
105 @custom_fields = IssueCustomField.find(:all)
104 @custom_fields = IssueCustomField.find(:all)
106 @issue_category ||= IssueCategory.new
105 @issue_category ||= IssueCategory.new
107 @member ||= @project.members.new
106 @member ||= @project.members.new
108 @trackers = Tracker.all
107 @trackers = Tracker.all
109 @custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
108 @custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
110 @repository ||= @project.repository
109 @repository ||= @project.repository
111 @wiki ||= @project.wiki
110 @wiki ||= @project.wiki
112 end
111 end
113
112
114 # Edit @project
113 # Edit @project
115 def edit
114 def edit
116 if request.post?
115 if request.post?
117 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
116 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
118 if params[:custom_fields]
117 if params[:custom_fields]
119 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
118 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
120 @project.custom_values = @custom_values
119 @project.custom_values = @custom_values
121 end
120 end
122 @project.attributes = params[:project]
121 @project.attributes = params[:project]
123 if @project.save
122 if @project.save
124 flash[:notice] = l(:notice_successful_update)
123 flash[:notice] = l(:notice_successful_update)
125 redirect_to :action => 'settings', :id => @project
124 redirect_to :action => 'settings', :id => @project
126 else
125 else
127 settings
126 settings
128 render :action => 'settings'
127 render :action => 'settings'
129 end
128 end
130 end
129 end
131 end
130 end
132
131
133 def modules
132 def modules
134 @project.enabled_module_names = params[:enabled_modules]
133 @project.enabled_module_names = params[:enabled_modules]
135 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
134 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
136 end
135 end
137
136
138 def archive
137 def archive
139 @project.archive if request.post? && @project.active?
138 @project.archive if request.post? && @project.active?
140 redirect_to :controller => 'admin', :action => 'projects'
139 redirect_to :controller => 'admin', :action => 'projects'
141 end
140 end
142
141
143 def unarchive
142 def unarchive
144 @project.unarchive if request.post? && !@project.active?
143 @project.unarchive if request.post? && !@project.active?
145 redirect_to :controller => 'admin', :action => 'projects'
144 redirect_to :controller => 'admin', :action => 'projects'
146 end
145 end
147
146
148 # Delete @project
147 # Delete @project
149 def destroy
148 def destroy
150 @project_to_destroy = @project
149 @project_to_destroy = @project
151 if request.post? and params[:confirm]
150 if request.post? and params[:confirm]
152 @project_to_destroy.destroy
151 @project_to_destroy.destroy
153 redirect_to :controller => 'admin', :action => 'projects'
152 redirect_to :controller => 'admin', :action => 'projects'
154 end
153 end
155 # hide project in layout
154 # hide project in layout
156 @project = nil
155 @project = nil
157 end
156 end
158
157
159 # Add a new issue category to @project
158 # Add a new issue category to @project
160 def add_issue_category
159 def add_issue_category
161 @category = @project.issue_categories.build(params[:category])
160 @category = @project.issue_categories.build(params[:category])
162 if request.post? and @category.save
161 if request.post? and @category.save
163 respond_to do |format|
162 respond_to do |format|
164 format.html do
163 format.html do
165 flash[:notice] = l(:notice_successful_create)
164 flash[:notice] = l(:notice_successful_create)
166 redirect_to :action => 'settings', :tab => 'categories', :id => @project
165 redirect_to :action => 'settings', :tab => 'categories', :id => @project
167 end
166 end
168 format.js do
167 format.js do
169 # IE doesn't support the replace_html rjs method for select box options
168 # IE doesn't support the replace_html rjs method for select box options
170 render(:update) {|page| page.replace "issue_category_id",
169 render(:update) {|page| page.replace "issue_category_id",
171 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
170 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
172 }
171 }
173 end
172 end
174 end
173 end
175 end
174 end
176 end
175 end
177
176
178 # Add a new version to @project
177 # Add a new version to @project
179 def add_version
178 def add_version
180 @version = @project.versions.build(params[:version])
179 @version = @project.versions.build(params[:version])
181 if request.post? and @version.save
180 if request.post? and @version.save
182 flash[:notice] = l(:notice_successful_create)
181 flash[:notice] = l(:notice_successful_create)
183 redirect_to :action => 'settings', :tab => 'versions', :id => @project
182 redirect_to :action => 'settings', :tab => 'versions', :id => @project
184 end
183 end
185 end
184 end
186
185
187 # Add a new issue to @project
188 # The new issue will be created from an existing one if copy_from parameter is given
189 def add_issue
190 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
191 @issue.project = @project
192 @issue.author = User.current
193 @issue.tracker ||= @project.trackers.find(params[:tracker_id])
194
195 default_status = IssueStatus.default
196 unless default_status
197 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
198 render :nothing => true, :layout => true
199 return
200 end
201 @issue.status = default_status
202 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker))
203
204 if request.get?
205 @issue.start_date ||= Date.today
206 @custom_values = @issue.custom_values.empty? ?
207 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
208 @issue.custom_values
209 else
210 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
211 # Check that the user is allowed to apply the requested status
212 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
213 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
214 @issue.custom_values = @custom_values
215 if @issue.save
216 attach_files(@issue, params[:attachments])
217 flash[:notice] = l(:notice_successful_create)
218 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
219 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
220 return
221 end
222 end
223 @priorities = Enumeration::get_values('IPRI')
224 end
225
226 # Bulk edit issues
186 # Bulk edit issues
227 def bulk_edit_issues
187 def bulk_edit_issues
228 if request.post?
188 if request.post?
229 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
189 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
230 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
190 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
231 assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
191 assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
232 category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
192 category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
233 fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
193 fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
234 issues = @project.issues.find_all_by_id(params[:issue_ids])
194 issues = @project.issues.find_all_by_id(params[:issue_ids])
235 unsaved_issue_ids = []
195 unsaved_issue_ids = []
236 issues.each do |issue|
196 issues.each do |issue|
237 journal = issue.init_journal(User.current, params[:notes])
197 journal = issue.init_journal(User.current, params[:notes])
238 issue.priority = priority if priority
198 issue.priority = priority if priority
239 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
199 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
240 issue.category = category if category
200 issue.category = category if category
241 issue.fixed_version = fixed_version if fixed_version
201 issue.fixed_version = fixed_version if fixed_version
242 issue.start_date = params[:start_date] unless params[:start_date].blank?
202 issue.start_date = params[:start_date] unless params[:start_date].blank?
243 issue.due_date = params[:due_date] unless params[:due_date].blank?
203 issue.due_date = params[:due_date] unless params[:due_date].blank?
244 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
204 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
245 # Don't save any change to the issue if the user is not authorized to apply the requested status
205 # Don't save any change to the issue if the user is not authorized to apply the requested status
246 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
206 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
247 # Send notification for each issue (if changed)
207 # Send notification for each issue (if changed)
248 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
208 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
249 else
209 else
250 # Keep unsaved issue ids to display them in flash error
210 # Keep unsaved issue ids to display them in flash error
251 unsaved_issue_ids << issue.id
211 unsaved_issue_ids << issue.id
252 end
212 end
253 end
213 end
254 if unsaved_issue_ids.empty?
214 if unsaved_issue_ids.empty?
255 flash[:notice] = l(:notice_successful_update) unless issues.empty?
215 flash[:notice] = l(:notice_successful_update) unless issues.empty?
256 else
216 else
257 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
217 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
258 end
218 end
259 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
219 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
260 return
220 return
261 end
221 end
262 # Find potential statuses the user could be allowed to switch issues to
222 # Find potential statuses the user could be allowed to switch issues to
263 @available_statuses = Workflow.find(:all, :include => :new_status,
223 @available_statuses = Workflow.find(:all, :include => :new_status,
264 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
224 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
265 render :update do |page|
225 render :update do |page|
266 page.hide 'query_form'
226 page.hide 'query_form'
267 page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
227 page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
268 end
228 end
269 end
229 end
270
230
271 def move_issues
231 def move_issues
272 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
232 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
273 redirect_to :controller => 'issues', :action => 'index', :project_id => @project and return unless @issues
233 redirect_to :controller => 'issues', :action => 'index', :project_id => @project and return unless @issues
274
234
275 @projects = []
235 @projects = []
276 # find projects to which the user is allowed to move the issue
236 # find projects to which the user is allowed to move the issue
277 if User.current.admin?
237 if User.current.admin?
278 # admin is allowed to move issues to any active (visible) project
238 # admin is allowed to move issues to any active (visible) project
279 @projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
239 @projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
280 else
240 else
281 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:move_issues)}
241 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:move_issues)}
282 end
242 end
283 @target_project = @projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
243 @target_project = @projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
284 @target_project ||= @project
244 @target_project ||= @project
285 @trackers = @target_project.trackers
245 @trackers = @target_project.trackers
286 if request.post?
246 if request.post?
287 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
247 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
288 unsaved_issue_ids = []
248 unsaved_issue_ids = []
289 @issues.each do |issue|
249 @issues.each do |issue|
290 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
250 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
291 end
251 end
292 if unsaved_issue_ids.empty?
252 if unsaved_issue_ids.empty?
293 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
253 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
294 else
254 else
295 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
255 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
296 end
256 end
297 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
257 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
298 return
258 return
299 end
259 end
300 render :layout => false if request.xhr?
260 render :layout => false if request.xhr?
301 end
261 end
302
262
303 # Add a news to @project
263 # Add a news to @project
304 def add_news
264 def add_news
305 @news = News.new(:project => @project, :author => User.current)
265 @news = News.new(:project => @project, :author => User.current)
306 if request.post?
266 if request.post?
307 @news.attributes = params[:news]
267 @news.attributes = params[:news]
308 if @news.save
268 if @news.save
309 flash[:notice] = l(:notice_successful_create)
269 flash[:notice] = l(:notice_successful_create)
310 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
270 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
311 redirect_to :controller => 'news', :action => 'index', :project_id => @project
271 redirect_to :controller => 'news', :action => 'index', :project_id => @project
312 end
272 end
313 end
273 end
314 end
274 end
315
275
316 def add_file
276 def add_file
317 if request.post?
277 if request.post?
318 @version = @project.versions.find_by_id(params[:version_id])
278 @version = @project.versions.find_by_id(params[:version_id])
319 attachments = attach_files(@version, params[:attachments])
279 attachments = attach_files(@version, params[:attachments])
320 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
280 Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
321 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
281 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
322 end
282 end
323 @versions = @project.versions.sort
283 @versions = @project.versions.sort
324 end
284 end
325
285
326 def list_files
286 def list_files
327 @versions = @project.versions.sort
287 @versions = @project.versions.sort
328 end
288 end
329
289
330 # Show changelog for @project
290 # Show changelog for @project
331 def changelog
291 def changelog
332 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
292 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
333 retrieve_selected_tracker_ids(@trackers)
293 retrieve_selected_tracker_ids(@trackers)
334 @versions = @project.versions.sort
294 @versions = @project.versions.sort
335 end
295 end
336
296
337 def roadmap
297 def roadmap
338 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
298 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
339 retrieve_selected_tracker_ids(@trackers)
299 retrieve_selected_tracker_ids(@trackers)
340 @versions = @project.versions.sort
300 @versions = @project.versions.sort
341 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
301 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
342 end
302 end
343
303
344 def activity
304 def activity
345 if params[:year] and params[:year].to_i > 1900
305 if params[:year] and params[:year].to_i > 1900
346 @year = params[:year].to_i
306 @year = params[:year].to_i
347 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
307 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
348 @month = params[:month].to_i
308 @month = params[:month].to_i
349 end
309 end
350 end
310 end
351 @year ||= Date.today.year
311 @year ||= Date.today.year
352 @month ||= Date.today.month
312 @month ||= Date.today.month
353
313
354 case params[:format]
314 case params[:format]
355 when 'atom'
315 when 'atom'
356 # 30 last days
316 # 30 last days
357 @date_from = Date.today - 30
317 @date_from = Date.today - 30
358 @date_to = Date.today + 1
318 @date_to = Date.today + 1
359 else
319 else
360 # current month
320 # current month
361 @date_from = Date.civil(@year, @month, 1)
321 @date_from = Date.civil(@year, @month, 1)
362 @date_to = @date_from >> 1
322 @date_to = @date_from >> 1
363 end
323 end
364
324
365 @event_types = %w(issues news files documents changesets wiki_pages messages)
325 @event_types = %w(issues news files documents changesets wiki_pages messages)
366 @event_types.delete('wiki_pages') unless @project.wiki
326 @event_types.delete('wiki_pages') unless @project.wiki
367 @event_types.delete('changesets') unless @project.repository
327 @event_types.delete('changesets') unless @project.repository
368 @event_types.delete('messages') unless @project.boards.any?
328 @event_types.delete('messages') unless @project.boards.any?
369 # only show what the user is allowed to view
329 # only show what the user is allowed to view
370 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
330 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
371
331
372 @scope = @event_types.select {|t| params["show_#{t}"]}
332 @scope = @event_types.select {|t| params["show_#{t}"]}
373 # default events if none is specified in parameters
333 # default events if none is specified in parameters
374 @scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
334 @scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
375
335
376 @events = []
336 @events = []
377
337
378 if @scope.include?('issues')
338 if @scope.include?('issues')
379 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
339 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
380 @events += @project.issues_status_changes(@date_from, @date_to)
340 @events += @project.issues_status_changes(@date_from, @date_to)
381 end
341 end
382
342
383 if @scope.include?('news')
343 if @scope.include?('news')
384 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
344 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
385 end
345 end
386
346
387 if @scope.include?('files')
347 if @scope.include?('files')
388 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
348 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
389 end
349 end
390
350
391 if @scope.include?('documents')
351 if @scope.include?('documents')
392 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
352 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
393 @events += Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
353 @events += Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
394 end
354 end
395
355
396 if @scope.include?('wiki_pages')
356 if @scope.include?('wiki_pages')
397 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
357 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
398 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
358 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
399 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
359 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
400 "#{WikiContent.versioned_table_name}.id"
360 "#{WikiContent.versioned_table_name}.id"
401 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
361 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
402 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
362 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
403 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
363 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
404 @project.id, @date_from, @date_to]
364 @project.id, @date_from, @date_to]
405
365
406 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
366 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
407 end
367 end
408
368
409 if @scope.include?('changesets')
369 if @scope.include?('changesets')
410 @events += Changeset.find(:all, :include => :repository, :conditions => ["#{Repository.table_name}.project_id = ? AND #{Changeset.table_name}.committed_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
370 @events += Changeset.find(:all, :include => :repository, :conditions => ["#{Repository.table_name}.project_id = ? AND #{Changeset.table_name}.committed_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
411 end
371 end
412
372
413 if @scope.include?('messages')
373 if @scope.include?('messages')
414 @events += Message.find(:all,
374 @events += Message.find(:all,
415 :include => [:board, :author],
375 :include => [:board, :author],
416 :conditions => ["#{Board.table_name}.project_id=? AND #{Message.table_name}.parent_id IS NULL AND #{Message.table_name}.created_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
376 :conditions => ["#{Board.table_name}.project_id=? AND #{Message.table_name}.parent_id IS NULL AND #{Message.table_name}.created_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
417 end
377 end
418
378
419 @events_by_day = @events.group_by(&:event_date)
379 @events_by_day = @events.group_by(&:event_date)
420
380
421 respond_to do |format|
381 respond_to do |format|
422 format.html { render :layout => false if request.xhr? }
382 format.html { render :layout => false if request.xhr? }
423 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
383 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
424 end
384 end
425 end
385 end
426
386
427 def calendar
387 def calendar
428 @trackers = @project.rolled_up_trackers
388 @trackers = @project.rolled_up_trackers
429 retrieve_selected_tracker_ids(@trackers)
389 retrieve_selected_tracker_ids(@trackers)
430
390
431 if params[:year] and params[:year].to_i > 1900
391 if params[:year] and params[:year].to_i > 1900
432 @year = params[:year].to_i
392 @year = params[:year].to_i
433 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
393 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
434 @month = params[:month].to_i
394 @month = params[:month].to_i
435 end
395 end
436 end
396 end
437 @year ||= Date.today.year
397 @year ||= Date.today.year
438 @month ||= Date.today.month
398 @month ||= Date.today.month
439 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
399 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
440
400
441 events = []
401 events = []
442 @project.issues_with_subprojects(params[:with_subprojects]) do
402 @project.issues_with_subprojects(params[:with_subprojects]) do
443 events += Issue.find(:all,
403 events += Issue.find(:all,
444 :include => [:tracker, :status, :assigned_to, :priority, :project],
404 :include => [:tracker, :status, :assigned_to, :priority, :project],
445 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
405 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
446 ) unless @selected_tracker_ids.empty?
406 ) unless @selected_tracker_ids.empty?
447 end
407 end
448 events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
408 events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
449 @calendar.events = events
409 @calendar.events = events
450
410
451 render :layout => false if request.xhr?
411 render :layout => false if request.xhr?
452 end
412 end
453
413
454 def gantt
414 def gantt
455 @trackers = @project.rolled_up_trackers
415 @trackers = @project.rolled_up_trackers
456 retrieve_selected_tracker_ids(@trackers)
416 retrieve_selected_tracker_ids(@trackers)
457
417
458 if params[:year] and params[:year].to_i >0
418 if params[:year] and params[:year].to_i >0
459 @year_from = params[:year].to_i
419 @year_from = params[:year].to_i
460 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
420 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
461 @month_from = params[:month].to_i
421 @month_from = params[:month].to_i
462 else
422 else
463 @month_from = 1
423 @month_from = 1
464 end
424 end
465 else
425 else
466 @month_from ||= Date.today.month
426 @month_from ||= Date.today.month
467 @year_from ||= Date.today.year
427 @year_from ||= Date.today.year
468 end
428 end
469
429
470 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
430 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
471 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
431 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
472 months = (params[:months] || User.current.pref[:gantt_months]).to_i
432 months = (params[:months] || User.current.pref[:gantt_months]).to_i
473 @months = (months > 0 && months < 25) ? months : 6
433 @months = (months > 0 && months < 25) ? months : 6
474
434
475 # Save gantt paramters as user preference (zoom and months count)
435 # Save gantt paramters as user preference (zoom and months count)
476 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
436 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
477 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
437 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
478 User.current.preference.save
438 User.current.preference.save
479 end
439 end
480
440
481 @date_from = Date.civil(@year_from, @month_from, 1)
441 @date_from = Date.civil(@year_from, @month_from, 1)
482 @date_to = (@date_from >> @months) - 1
442 @date_to = (@date_from >> @months) - 1
483
443
484 @events = []
444 @events = []
485 @project.issues_with_subprojects(params[:with_subprojects]) do
445 @project.issues_with_subprojects(params[:with_subprojects]) do
486 @events += Issue.find(:all,
446 @events += Issue.find(:all,
487 :order => "start_date, due_date",
447 :order => "start_date, due_date",
488 :include => [:tracker, :status, :assigned_to, :priority, :project],
448 :include => [:tracker, :status, :assigned_to, :priority, :project],
489 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
449 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
490 ) unless @selected_tracker_ids.empty?
450 ) unless @selected_tracker_ids.empty?
491 end
451 end
492 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
452 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
493 @events.sort! {|x,y| x.start_date <=> y.start_date }
453 @events.sort! {|x,y| x.start_date <=> y.start_date }
494
454
495 if params[:format]=='pdf'
455 if params[:format]=='pdf'
496 @options_for_rfpdf ||= {}
456 @options_for_rfpdf ||= {}
497 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
457 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
498 render :template => "projects/gantt.rfpdf", :layout => false
458 render :template => "projects/gantt.rfpdf", :layout => false
499 elsif params[:format]=='png' && respond_to?('gantt_image')
459 elsif params[:format]=='png' && respond_to?('gantt_image')
500 image = gantt_image(@events, @date_from, @months, @zoom)
460 image = gantt_image(@events, @date_from, @months, @zoom)
501 image.format = 'PNG'
461 image.format = 'PNG'
502 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
462 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
503 else
463 else
504 render :template => "projects/gantt.rhtml"
464 render :template => "projects/gantt.rhtml"
505 end
465 end
506 end
466 end
507
467
508 private
468 private
509 # Find project of id params[:id]
469 # Find project of id params[:id]
510 # if not found, redirect to project list
470 # if not found, redirect to project list
511 # Used as a before_filter
471 # Used as a before_filter
512 def find_project
472 def find_project
513 @project = Project.find(params[:id])
473 @project = Project.find(params[:id])
514 rescue ActiveRecord::RecordNotFound
474 rescue ActiveRecord::RecordNotFound
515 render_404
475 render_404
516 end
476 end
517
477
518 def retrieve_selected_tracker_ids(selectable_trackers)
478 def retrieve_selected_tracker_ids(selectable_trackers)
519 if ids = params[:tracker_ids]
479 if ids = params[:tracker_ids]
520 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
480 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
521 else
481 else
522 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
482 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
523 end
483 end
524 end
484 end
525 end
485 end
@@ -1,199 +1,199
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module ProjectsHelper
18 module ProjectsHelper
19 def link_to_version(version, options = {})
19 def link_to_version(version, options = {})
20 return '' unless version && version.is_a?(Version)
20 return '' unless version && version.is_a?(Version)
21 link_to version.name, {:controller => 'projects',
21 link_to version.name, {:controller => 'projects',
22 :action => 'roadmap',
22 :action => 'roadmap',
23 :id => version.project_id,
23 :id => version.project_id,
24 :completed => (version.completed? ? 1 : nil),
24 :completed => (version.completed? ? 1 : nil),
25 :anchor => version.name
25 :anchor => version.name
26 }, options
26 }, options
27 end
27 end
28
28
29 def project_settings_tabs
29 def project_settings_tabs
30 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
30 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
31 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
31 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
32 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
32 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
33 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
33 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
34 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
34 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
35 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
35 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
36 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
36 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
37 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}
37 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}
38 ]
38 ]
39 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
39 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
40 end
40 end
41
41
42 # Generates a gantt image
42 # Generates a gantt image
43 # Only defined if RMagick is avalaible
43 # Only defined if RMagick is avalaible
44 def gantt_image(events, date_from, months, zoom)
44 def gantt_image(events, date_from, months, zoom)
45 date_to = (date_from >> months)-1
45 date_to = (date_from >> months)-1
46 show_weeks = zoom > 1
46 show_weeks = zoom > 1
47 show_days = zoom > 2
47 show_days = zoom > 2
48
48
49 subject_width = 320
49 subject_width = 320
50 header_heigth = 18
50 header_heigth = 18
51 # width of one day in pixels
51 # width of one day in pixels
52 zoom = zoom*2
52 zoom = zoom*2
53 g_width = (date_to - date_from + 1)*zoom
53 g_width = (date_to - date_from + 1)*zoom
54 g_height = 20 * events.length + 20
54 g_height = 20 * events.length + 20
55 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
55 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
56 height = g_height + headers_heigth
56 height = g_height + headers_heigth
57
57
58 imgl = Magick::ImageList.new
58 imgl = Magick::ImageList.new
59 imgl.new_image(subject_width+g_width+1, height)
59 imgl.new_image(subject_width+g_width+1, height)
60 gc = Magick::Draw.new
60 gc = Magick::Draw.new
61
61
62 # Subjects
62 # Subjects
63 top = headers_heigth + 20
63 top = headers_heigth + 20
64 gc.fill('black')
64 gc.fill('black')
65 gc.stroke('transparent')
65 gc.stroke('transparent')
66 gc.stroke_width(1)
66 gc.stroke_width(1)
67 events.each do |i|
67 events.each do |i|
68 gc.text(4, top + 2, (i.is_a?(Issue) ? i.subject : i.name))
68 gc.text(4, top + 2, (i.is_a?(Issue) ? i.subject : i.name))
69 top = top + 20
69 top = top + 20
70 end
70 end
71
71
72 # Months headers
72 # Months headers
73 month_f = date_from
73 month_f = date_from
74 left = subject_width
74 left = subject_width
75 months.times do
75 months.times do
76 width = ((month_f >> 1) - month_f) * zoom
76 width = ((month_f >> 1) - month_f) * zoom
77 gc.fill('white')
77 gc.fill('white')
78 gc.stroke('grey')
78 gc.stroke('grey')
79 gc.stroke_width(1)
79 gc.stroke_width(1)
80 gc.rectangle(left, 0, left + width, height)
80 gc.rectangle(left, 0, left + width, height)
81 gc.fill('black')
81 gc.fill('black')
82 gc.stroke('transparent')
82 gc.stroke('transparent')
83 gc.stroke_width(1)
83 gc.stroke_width(1)
84 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
84 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
85 left = left + width
85 left = left + width
86 month_f = month_f >> 1
86 month_f = month_f >> 1
87 end
87 end
88
88
89 # Weeks headers
89 # Weeks headers
90 if show_weeks
90 if show_weeks
91 left = subject_width
91 left = subject_width
92 height = header_heigth
92 height = header_heigth
93 if date_from.cwday == 1
93 if date_from.cwday == 1
94 # date_from is monday
94 # date_from is monday
95 week_f = date_from
95 week_f = date_from
96 else
96 else
97 # find next monday after date_from
97 # find next monday after date_from
98 week_f = date_from + (7 - date_from.cwday + 1)
98 week_f = date_from + (7 - date_from.cwday + 1)
99 width = (7 - date_from.cwday + 1) * zoom
99 width = (7 - date_from.cwday + 1) * zoom
100 gc.fill('white')
100 gc.fill('white')
101 gc.stroke('grey')
101 gc.stroke('grey')
102 gc.stroke_width(1)
102 gc.stroke_width(1)
103 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
103 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
104 left = left + width
104 left = left + width
105 end
105 end
106 while week_f <= date_to
106 while week_f <= date_to
107 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
107 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
108 gc.fill('white')
108 gc.fill('white')
109 gc.stroke('grey')
109 gc.stroke('grey')
110 gc.stroke_width(1)
110 gc.stroke_width(1)
111 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
111 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
112 gc.fill('black')
112 gc.fill('black')
113 gc.stroke('transparent')
113 gc.stroke('transparent')
114 gc.stroke_width(1)
114 gc.stroke_width(1)
115 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
115 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
116 left = left + width
116 left = left + width
117 week_f = week_f+7
117 week_f = week_f+7
118 end
118 end
119 end
119 end
120
120
121 # Days details (week-end in grey)
121 # Days details (week-end in grey)
122 if show_days
122 if show_days
123 left = subject_width
123 left = subject_width
124 height = g_height + header_heigth - 1
124 height = g_height + header_heigth - 1
125 wday = date_from.cwday
125 wday = date_from.cwday
126 (date_to - date_from + 1).to_i.times do
126 (date_to - date_from + 1).to_i.times do
127 width = zoom
127 width = zoom
128 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
128 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
129 gc.stroke('grey')
129 gc.stroke('grey')
130 gc.stroke_width(1)
130 gc.stroke_width(1)
131 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
131 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
132 left = left + width
132 left = left + width
133 wday = wday + 1
133 wday = wday + 1
134 wday = 1 if wday > 7
134 wday = 1 if wday > 7
135 end
135 end
136 end
136 end
137
137
138 # border
138 # border
139 gc.fill('transparent')
139 gc.fill('transparent')
140 gc.stroke('grey')
140 gc.stroke('grey')
141 gc.stroke_width(1)
141 gc.stroke_width(1)
142 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
142 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
143 gc.stroke('black')
143 gc.stroke('black')
144 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
144 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
145
145
146 # content
146 # content
147 top = headers_heigth + 20
147 top = headers_heigth + 20
148 gc.stroke('transparent')
148 gc.stroke('transparent')
149 events.each do |i|
149 events.each do |i|
150 if i.is_a?(Issue)
150 if i.is_a?(Issue)
151 i_start_date = (i.start_date >= date_from ? i.start_date : date_from )
151 i_start_date = (i.start_date >= date_from ? i.start_date : date_from )
152 i_end_date = (i.due_date <= date_to ? i.due_date : date_to )
152 i_end_date = (i.due_date <= date_to ? i.due_date : date_to )
153 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
153 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
154 i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
154 i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
155 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
155 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
156 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
156 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
157
157
158 i_left = subject_width + ((i_start_date - date_from)*zoom).floor
158 i_left = subject_width + ((i_start_date - date_from)*zoom).floor
159 i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue
159 i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue
160 d_width = ((i_done_date - i_start_date)*zoom).floor # done width
160 d_width = ((i_done_date - i_start_date)*zoom).floor # done width
161 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width
161 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width
162
162
163 gc.fill('grey')
163 gc.fill('grey')
164 gc.rectangle(i_left, top, i_left + i_width, top - 6)
164 gc.rectangle(i_left, top, i_left + i_width, top - 6)
165 gc.fill('red')
165 gc.fill('red')
166 gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0
166 gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0
167 gc.fill('blue')
167 gc.fill('blue')
168 gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0
168 gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0
169 gc.fill('black')
169 gc.fill('black')
170 gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%")
170 gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%")
171 else
171 else
172 i_left = subject_width + ((i.start_date - date_from)*zoom).floor
172 i_left = subject_width + ((i.start_date - date_from)*zoom).floor
173 gc.fill('green')
173 gc.fill('green')
174 gc.rectangle(i_left, top, i_left + 6, top - 6)
174 gc.rectangle(i_left, top, i_left + 6, top - 6)
175 gc.fill('black')
175 gc.fill('black')
176 gc.text(i_left + 11, top + 1, i.name)
176 gc.text(i_left + 11, top + 1, i.name)
177 end
177 end
178 top = top + 20
178 top = top + 20
179 end
179 end
180
180
181 # today red line
181 # today red line
182 if Date.today >= date_from and Date.today <= date_to
182 if Date.today >= date_from and Date.today <= date_to
183 gc.stroke('red')
183 gc.stroke('red')
184 x = (Date.today-date_from+1)*zoom + subject_width
184 x = (Date.today-date_from+1)*zoom + subject_width
185 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
185 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
186 end
186 end
187
187
188 gc.draw(imgl)
188 gc.draw(imgl)
189 imgl
189 imgl
190 end if Object.const_defined?(:Magick)
190 end if Object.const_defined?(:Magick)
191
191
192 def new_issue_selector
192 def new_issue_selector
193 trackers = @project.trackers
193 trackers = @project.trackers
194 # can't use form tag inside helper
194 # can't use form tag inside helper
195 content_tag('form',
195 content_tag('form',
196 select_tag('tracker_id', '<option></option>' + options_from_collection_for_select(trackers, 'id', 'name'), :onchange => "if (this.value != '') {this.form.submit()}"),
196 select_tag('tracker_id', '<option></option>' + options_from_collection_for_select(trackers, 'id', 'name'), :onchange => "if (this.value != '') {this.form.submit()}"),
197 :action => url_for(:controller => 'projects', :action => 'add_issue', :id => @project), :method => 'get')
197 :action => url_for(:controller => 'issues', :action => 'new', :project_id => @project), :method => 'get')
198 end
198 end
199 end
199 end
@@ -1,52 +1,59
1 <%= error_messages_for 'issue' %>
1 <%= error_messages_for 'issue' %>
2 <div class="box">
2 <div class="box">
3
3
4 <% if @issue.new_record? %>
5 <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
6 <%= observe_field :issue_tracker_id, :url => { :action => :new },
7 :update => :content,
8 :with => "Form.serialize('issue-form')" %>
9 <% end %>
10
4 <div class="splitcontentleft">
11 <div class="splitcontentleft">
5 <% if @issue.new_record? %>
12 <% if @issue.new_record? %>
6 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
13 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
7 <% else %>
14 <% else %>
8 <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p>
15 <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p>
9 <% end %>
16 <% end %>
10
17
11 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
18 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
12 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
19 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
13 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
20 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
14 <%= prompt_to_remote(l(:label_issue_category_new),
21 <%= prompt_to_remote(l(:label_issue_category_new),
15 l(:label_issue_category_new), 'category[name]',
22 l(:label_issue_category_new), 'category[name]',
16 {:controller => 'projects', :action => 'add_issue_category', :id => @project},
23 {:controller => 'projects', :action => 'add_issue_category', :id => @project},
17 :class => 'small') if authorize_for('projects', 'add_issue_category') %></p>
24 :class => 'small') if authorize_for('projects', 'add_issue_category') %></p>
18 </div>
25 </div>
19
26
20 <div class="splitcontentright">
27 <div class="splitcontentright">
21 <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
28 <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
22 <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
29 <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
23 <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
30 <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
24 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
31 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
25 </div>
32 </div>
26
33
27 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
34 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
28 <p><%= f.text_area :description, :required => true,
35 <p><%= f.text_area :description, :required => true,
29 :cols => 60,
36 :cols => 60,
30 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
37 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
31 :accesskey => accesskey(:edit),
38 :accesskey => accesskey(:edit),
32 :class => 'wiki-edit' %></p>
39 :class => 'wiki-edit' %></p>
33 <p><%= f.select :fixed_version_id, (@project.versions.sort.collect {|v| [v.name, v.id]}), { :include_blank => true } %></p>
40 <p><%= f.select :fixed_version_id, (@project.versions.sort.collect {|v| [v.name, v.id]}), { :include_blank => true } %></p>
34 <% for @custom_value in @custom_values %>
41 <% for @custom_value in @custom_values %>
35 <p><%= custom_field_tag_with_label @custom_value %></p>
42 <p><%= custom_field_tag_with_label @custom_value %></p>
36 <% end %>
43 <% end %>
37
44
38 <% if @issue.new_record? %>
45 <% if @issue.new_record? %>
39 <p id="attachments_p"><label for="attachment_file"><%=l(:label_attachment)%>
46 <p id="attachments_p"><label for="attachment_file"><%=l(:label_attachment)%>
40 <%= image_to_function "add.png", "addFileField();return false" %></label>
47 <%= image_to_function "add.png", "addFileField();return false" %></label>
41 <%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
48 <%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
42 <% end %>
49 <% end %>
43 </div>
50 </div>
44
51
45 <%= wikitoolbar_for 'issue_description' %>
52 <%= wikitoolbar_for 'issue_description' %>
46
53
47 <% content_for :header_tags do %>
54 <% content_for :header_tags do %>
48 <%= javascript_include_tag 'calendar/calendar' %>
55 <%= javascript_include_tag 'calendar/calendar' %>
49 <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
56 <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
50 <%= javascript_include_tag 'calendar/calendar-setup' %>
57 <%= javascript_include_tag 'calendar/calendar-setup' %>
51 <%= stylesheet_link_tag 'calendar' %>
58 <%= stylesheet_link_tag 'calendar' %>
52 <% end %>
59 <% end %>
@@ -1,18 +1,18
1 <% if authorize_for('projects', 'add_issue') && @project.trackers.any? %>
1 <% if authorize_for('issues', 'new') && @project.trackers.any? %>
2 <h3><%= l(:label_issue_new) %></h3>
2 <h3><%= l(:label_issue_new) %></h3>
3 <%= l(:label_tracker) %>: <%= new_issue_selector %>
3 <%= l(:label_tracker) %>: <%= new_issue_selector %>
4 <% end %>
4 <% end %>
5
5
6 <h3><%= l(:label_issue_plural) %></h3>
6 <h3><%= l(:label_issue_plural) %></h3>
7 <%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
7 <%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
8 <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
8 <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
9 <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
9 <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
10
10
11 <h3><%= l(:label_query_plural) %></h3>
11 <h3><%= l(:label_query_plural) %></h3>
12
12
13 <% queries = @project.queries.find(:all,
13 <% queries = @project.queries.find(:all,
14 :order => "name ASC",
14 :order => "name ASC",
15 :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
15 :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
16 queries.each do |query| %>
16 queries.each do |query| %>
17 <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br />
17 <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br />
18 <% end %>
18 <% end %>
@@ -1,40 +1,40
1 <% back_to = url_for(:controller => 'issues', :action => 'index', :project_id => @project) %>
1 <% back_to = url_for(:controller => 'issues', :action => 'index', :project_id => @project) %>
2 <ul>
2 <ul>
3 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
3 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
4 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
4 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
5 <li class="folder">
5 <li class="folder">
6 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
6 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
7 <ul>
7 <ul>
8 <% @statuses.each do |s| %>
8 <% @statuses.each do |s| %>
9 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:status_id => s}},
9 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:status_id => s}},
10 :selected => (s == @issue.status), :disabled => !(@allowed_statuses.include?(s)) %></li>
10 :selected => (s == @issue.status), :disabled => !(@allowed_statuses.include?(s)) %></li>
11 <% end %>
11 <% end %>
12 </ul>
12 </ul>
13 </li>
13 </li>
14 <li class="folder">
14 <li class="folder">
15 <a href="#" class="submenu"><%= l(:field_priority) %></a>
15 <a href="#" class="submenu"><%= l(:field_priority) %></a>
16 <ul>
16 <ul>
17 <% @priorities.each do |p| %>
17 <% @priorities.each do |p| %>
18 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => back_to}, :method => :post,
18 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => back_to}, :method => :post,
19 :selected => (p == @issue.priority), :disabled => !@can[:edit] %></li>
19 :selected => (p == @issue.priority), :disabled => !@can[:edit] %></li>
20 <% end %>
20 <% end %>
21 </ul>
21 </ul>
22 </li>
22 </li>
23 <li class="folder">
23 <li class="folder">
24 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
24 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
25 <ul>
25 <ul>
26 <% @assignables.each do |u| %>
26 <% @assignables.each do |u| %>
27 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:assigned_to_id => u}, :back_to => back_to}, :method => :post,
27 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:assigned_to_id => u}, :back_to => back_to}, :method => :post,
28 :selected => (u == @issue.assigned_to), :disabled => !@can[:assign] %></li>
28 :selected => (u == @issue.assigned_to), :disabled => !@can[:assign] %></li>
29 <% end %>
29 <% end %>
30 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:assigned_to_id => nil}, :back_to => back_to}, :method => :post,
30 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:assigned_to_id => nil}, :back_to => back_to}, :method => :post,
31 :selected => @issue.assigned_to.nil?, :disabled => !@can[:assign] %></li>
31 :selected => @issue.assigned_to.nil?, :disabled => !@can[:assign] %></li>
32 </ul>
32 </ul>
33 </li>
33 </li>
34 <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},
34 <li><%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
35 :class => 'icon-copy', :disabled => !@can[:copy] %></li>
35 :class => 'icon-copy', :disabled => !@can[:copy] %></li>
36 <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
36 <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
37 :class => 'icon-move', :disabled => !@can[:move] %>
37 :class => 'icon-move', :disabled => !@can[:move] %>
38 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
38 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
39 :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
39 :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
40 </ul>
40 </ul>
@@ -1,19 +1,17
1 <h2><%=l(:label_issue_new)%>: <%= @issue.tracker %></h2>
1 <h2><%=l(:label_issue_new)%>: <%= @issue.tracker %></h2>
2
2
3 <% labelled_tabular_form_for :issue, @issue,
3 <% labelled_tabular_form_for :issue, @issue,
4 :url => {:action => 'add_issue'},
5 :html => {:multipart => true, :id => 'issue-form'} do |f| %>
4 :html => {:multipart => true, :id => 'issue-form'} do |f| %>
6 <%= f.hidden_field :tracker_id %>
7 <%= render :partial => 'issues/form', :locals => {:f => f} %>
5 <%= render :partial => 'issues/form', :locals => {:f => f} %>
8 <%= submit_tag l(:button_create) %>
6 <%= submit_tag l(:button_create) %>
9 <%= link_to_remote l(:label_preview),
7 <%= link_to_remote l(:label_preview),
10 { :url => { :controller => 'issues', :action => 'preview', :id => @issue },
8 { :url => { :controller => 'issues', :action => 'preview', :id => @issue },
11 :method => 'post',
9 :method => 'post',
12 :update => 'preview',
10 :update => 'preview',
13 :with => "Form.serialize('issue-form')",
11 :with => "Form.serialize('issue-form')",
14 :complete => "location.href='#preview-top'"
12 :complete => "location.href='#preview-top'"
15 }, :accesskey => accesskey(:preview) %>
13 }, :accesskey => accesskey(:preview) %>
16 <% end %>
14 <% end %>
17
15
18 <a name="preview-top"></a>
16 <a name="preview-top"></a>
19 <div id="preview" class="wiki"></div>
17 <div id="preview" class="wiki"></div>
@@ -1,109 +1,109
1 <div class="contextual">
1 <div class="contextual">
2 <%= show_and_goto_link(l(:button_update), 'update', :class => 'icon icon-note') if authorize_for('issues', 'update') %>
2 <%= show_and_goto_link(l(:button_update), 'update', :class => 'icon icon-note') if authorize_for('issues', 'update') %>
3 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
3 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
4 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
4 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
5 <%= watcher_tag(@issue, User.current) %>
5 <%= watcher_tag(@issue, User.current) %>
6 <%= link_to_if_authorized l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
6 <%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
7 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
7 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
8 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
8 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
9 </div>
9 </div>
10
10
11 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
11 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
12
12
13 <div class="issue <%= "status-#{@issue.status.position} priority-#{@issue.priority.position}" %>">
13 <div class="issue <%= "status-#{@issue.status.position} priority-#{@issue.priority.position}" %>">
14 <h3><%=h @issue.subject %></h3>
14 <h3><%=h @issue.subject %></h3>
15 <p class="author">
15 <p class="author">
16 <%= authoring @issue.created_on, @issue.author %>.
16 <%= authoring @issue.created_on, @issue.author %>.
17 <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) if @issue.created_on != @issue.updated_on %>.
17 <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) if @issue.created_on != @issue.updated_on %>.
18 </p>
18 </p>
19
19
20 <table width="100%">
20 <table width="100%">
21 <tr>
21 <tr>
22 <td style="width:15%"><b><%=l(:field_status)%> :</b></td><td style="width:35%"><%= @issue.status.name %></td>
22 <td style="width:15%"><b><%=l(:field_status)%> :</b></td><td style="width:35%"><%= @issue.status.name %></td>
23 <td style="width:15%"><b><%=l(:field_start_date)%> :</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
23 <td style="width:15%"><b><%=l(:field_start_date)%> :</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
24 </tr>
24 </tr>
25 <tr>
25 <tr>
26 <td><b><%=l(:field_priority)%> :</b></td><td><%= @issue.priority.name %></td>
26 <td><b><%=l(:field_priority)%> :</b></td><td><%= @issue.priority.name %></td>
27 <td><b><%=l(:field_due_date)%> :</b></td><td><%= format_date(@issue.due_date) %></td>
27 <td><b><%=l(:field_due_date)%> :</b></td><td><%= format_date(@issue.due_date) %></td>
28 </tr>
28 </tr>
29 <tr>
29 <tr>
30 <td><b><%=l(:field_assigned_to)%> :</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
30 <td><b><%=l(:field_assigned_to)%> :</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
31 <td><b><%=l(:field_done_ratio)%> :</b></td><td><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
31 <td><b><%=l(:field_done_ratio)%> :</b></td><td><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
32 </tr>
32 </tr>
33 <tr>
33 <tr>
34 <td><b><%=l(:field_category)%> :</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
34 <td><b><%=l(:field_category)%> :</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
35 <% if User.current.allowed_to?(:view_time_entries, @project) %>
35 <% if User.current.allowed_to?(:view_time_entries, @project) %>
36 <td><b><%=l(:label_spent_time)%> :</b></td>
36 <td><b><%=l(:label_spent_time)%> :</b></td>
37 <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
37 <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
38 <% end %>
38 <% end %>
39 </tr>
39 </tr>
40 <tr>
40 <tr>
41 <td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
41 <td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
42 <% if @issue.estimated_hours %>
42 <% if @issue.estimated_hours %>
43 <td><b><%=l(:field_estimated_hours)%> :</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
43 <td><b><%=l(:field_estimated_hours)%> :</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
44 <% end %>
44 <% end %>
45 </tr>
45 </tr>
46 <tr>
46 <tr>
47 <% n = 0
47 <% n = 0
48 for custom_value in @custom_values %>
48 for custom_value in @custom_values %>
49 <td valign="top"><b><%= custom_value.custom_field.name %> :</b></td><td valign="top"><%= simple_format(h(show_value(custom_value))) %></td>
49 <td valign="top"><b><%= custom_value.custom_field.name %> :</b></td><td valign="top"><%= simple_format(h(show_value(custom_value))) %></td>
50 <% n = n + 1
50 <% n = n + 1
51 if (n > 1)
51 if (n > 1)
52 n = 0 %>
52 n = 0 %>
53 </tr><tr>
53 </tr><tr>
54 <%end
54 <%end
55 end %>
55 end %>
56 </tr>
56 </tr>
57 </table>
57 </table>
58 <hr />
58 <hr />
59
59
60 <% if @issue.changesets.any? %>
60 <% if @issue.changesets.any? %>
61 <div style="float:right;">
61 <div style="float:right;">
62 <em><%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %></em>
62 <em><%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %></em>
63 </div>
63 </div>
64 <% end %>
64 <% end %>
65
65
66 <p><strong><%=l(:field_description)%></strong></p>
66 <p><strong><%=l(:field_description)%></strong></p>
67 <div class="wiki">
67 <div class="wiki">
68 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
68 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
69 </div>
69 </div>
70
70
71 <% if @issue.attachments.any? %>
71 <% if @issue.attachments.any? %>
72 <%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
72 <%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
73 <% end %>
73 <% end %>
74
74
75 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
75 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
76 <hr />
76 <hr />
77 <div id="relations">
77 <div id="relations">
78 <%= render :partial => 'relations' %>
78 <%= render :partial => 'relations' %>
79 </div>
79 </div>
80 <% end %>
80 <% end %>
81
81
82 </div>
82 </div>
83
83
84 <% if @journals.any? %>
84 <% if @journals.any? %>
85 <div id="history">
85 <div id="history">
86 <h3><%=l(:label_history)%></h3>
86 <h3><%=l(:label_history)%></h3>
87 <%= render :partial => 'history', :locals => { :journals => @journals } %>
87 <%= render :partial => 'history', :locals => { :journals => @journals } %>
88 </div>
88 </div>
89 <% end %>
89 <% end %>
90
90
91 <% if authorize_for('issues', 'update') %>
91 <% if authorize_for('issues', 'update') %>
92 <a name="update-anchor"></a>
92 <a name="update-anchor"></a>
93 <div id="update" style="display:none;">
93 <div id="update" style="display:none;">
94 <h3><%= l(:button_update) %></h3>
94 <h3><%= l(:button_update) %></h3>
95 <%= render :partial => 'update' %>
95 <%= render :partial => 'update' %>
96 <%= toggle_link l(:button_cancel), 'update' %>
96 <%= toggle_link l(:button_cancel), 'update' %>
97 </div>
97 </div>
98 <% end %>
98 <% end %>
99
99
100 <div class="contextual">
100 <div class="contextual">
101 <%= l(:label_export_to) %><%= link_to 'PDF', {:format => 'pdf'}, :class => 'icon icon-pdf' %>
101 <%= l(:label_export_to) %><%= link_to 'PDF', {:format => 'pdf'}, :class => 'icon icon-pdf' %>
102 </div>
102 </div>
103 &nbsp;
103 &nbsp;
104
104
105 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
105 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
106
106
107 <% content_for :sidebar do %>
107 <% content_for :sidebar do %>
108 <%= render :partial => 'issues/sidebar' %>
108 <%= render :partial => 'issues/sidebar' %>
109 <% end %>
109 <% end %>
@@ -1,85 +1,85
1 <h2><%=l(:label_overview)%></h2>
1 <h2><%=l(:label_overview)%></h2>
2
2
3 <div class="splitcontentleft">
3 <div class="splitcontentleft">
4 <%= textilizable @project.description %>
4 <%= textilizable @project.description %>
5 <ul>
5 <ul>
6 <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link @project.homepage %></li><% end %>
6 <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link @project.homepage %></li><% end %>
7 <% if @subprojects.any? %>
7 <% if @subprojects.any? %>
8 <li><%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(h(p.name), :action => 'show', :id => p)}.join(", ") %></li>
8 <li><%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(h(p.name), :action => 'show', :id => p)}.join(", ") %></li>
9 <% end %>
9 <% end %>
10 <% if @project.parent %>
10 <% if @project.parent %>
11 <li><%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %></li>
11 <li><%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %></li>
12 <% end %>
12 <% end %>
13 <% for custom_value in @custom_values %>
13 <% for custom_value in @custom_values %>
14 <% if !custom_value.value.empty? %>
14 <% if !custom_value.value.empty? %>
15 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
15 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
16 <% end %>
16 <% end %>
17 <% end %>
17 <% end %>
18 </ul>
18 </ul>
19
19
20 <% if User.current.allowed_to?(:view_issues, @project) %>
20 <% if User.current.allowed_to?(:view_issues, @project) %>
21 <div class="box">
21 <div class="box">
22 <h3 class="icon22 icon22-tracker"><%=l(:label_issue_tracking)%></h3>
22 <h3 class="icon22 icon22-tracker"><%=l(:label_issue_tracking)%></h3>
23 <ul>
23 <ul>
24 <% for tracker in @trackers %>
24 <% for tracker in @trackers %>
25 <li><%= link_to tracker.name, :controller => 'issues', :action => 'index', :project_id => @project,
25 <li><%= link_to tracker.name, :controller => 'issues', :action => 'index', :project_id => @project,
26 :set_filter => 1,
26 :set_filter => 1,
27 "tracker_id" => tracker.id %>:
27 "tracker_id" => tracker.id %>:
28 <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
28 <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
29 <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
29 <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
30 <% end %>
30 <% end %>
31 </ul>
31 </ul>
32 <p><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %></p>
32 <p><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %></p>
33 </div>
33 </div>
34 <% end %>
34 <% end %>
35 </div>
35 </div>
36
36
37 <div class="splitcontentright">
37 <div class="splitcontentright">
38 <% if @members_by_role.any? %>
38 <% if @members_by_role.any? %>
39 <div class="box">
39 <div class="box">
40 <h3 class="icon22 icon22-users"><%=l(:label_member_plural)%></h3>
40 <h3 class="icon22 icon22-users"><%=l(:label_member_plural)%></h3>
41 <p><% @members_by_role.keys.sort.each do |role| %>
41 <p><% @members_by_role.keys.sort.each do |role| %>
42 <%= role.name %>:
42 <%= role.name %>:
43 <%= @members_by_role[role].collect(&:user).sort.collect{|u| link_to_user u}.join(", ") %>
43 <%= @members_by_role[role].collect(&:user).sort.collect{|u| link_to_user u}.join(", ") %>
44 <br />
44 <br />
45 <% end %></p>
45 <% end %></p>
46 </div>
46 </div>
47 <% end %>
47 <% end %>
48
48
49 <% if @news.any? && authorize_for('news', 'index') %>
49 <% if @news.any? && authorize_for('news', 'index') %>
50 <div class="box">
50 <div class="box">
51 <h3><%=l(:label_news_latest)%></h3>
51 <h3><%=l(:label_news_latest)%></h3>
52 <%= render :partial => 'news/news', :collection => @news %>
52 <%= render :partial => 'news/news', :collection => @news %>
53 <p><%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %></p>
53 <p><%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %></p>
54 </div>
54 </div>
55 <% end %>
55 <% end %>
56 </div>
56 </div>
57
57
58 <% content_for :sidebar do %>
58 <% content_for :sidebar do %>
59 <% if authorize_for('projects', 'add_issue') && @project.trackers.any? %>
59 <% if authorize_for('issues', 'new') && @project.trackers.any? %>
60 <h3><%= l(:label_issue_new) %></h3>
60 <h3><%= l(:label_issue_new) %></h3>
61 <%= l(:label_tracker) %>: <%= new_issue_selector %>
61 <%= l(:label_tracker) %>: <%= new_issue_selector %>
62 <% end %>
62 <% end %>
63
63
64 <% planning_links = []
64 <% planning_links = []
65 planning_links << link_to_if_authorized(l(:label_calendar), :action => 'calendar', :id => @project)
65 planning_links << link_to_if_authorized(l(:label_calendar), :action => 'calendar', :id => @project)
66 planning_links << link_to_if_authorized(l(:label_gantt), :action => 'gantt', :id => @project)
66 planning_links << link_to_if_authorized(l(:label_gantt), :action => 'gantt', :id => @project)
67 planning_links.compact!
67 planning_links.compact!
68 unless planning_links.empty? %>
68 unless planning_links.empty? %>
69 <h3>Planning</h3>
69 <h3>Planning</h3>
70 <p><%= planning_links.join(' | ') %></p>
70 <p><%= planning_links.join(' | ') %></p>
71 <% end %>
71 <% end %>
72
72
73 <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>
73 <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>
74 <h3><%= l(:label_spent_time) %></h3>
74 <h3><%= l(:label_spent_time) %></h3>
75 <p><span class="icon icon-time"><%= lwr(:label_f_hour, @total_hours) %></span></p>
75 <p><span class="icon icon-time"><%= lwr(:label_f_hour, @total_hours) %></span></p>
76 <p><%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}) %> |
76 <p><%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}) %> |
77 <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}) %></p>
77 <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}) %></p>
78 <% end %>
78 <% end %>
79 <% end %>
79 <% end %>
80
80
81 <% content_for :header_tags do %>
81 <% content_for :header_tags do %>
82 <%= auto_discovery_link_tag(:atom, {:action => 'activity', :id => @project, :format => 'atom', :key => User.current.rss_key}) %>
82 <%= auto_discovery_link_tag(:atom, {:action => 'activity', :id => @project, :format => 'atom', :key => User.current.rss_key}) %>
83 <% end %>
83 <% end %>
84
84
85 <% html_title(l(:label_overview)) -%>
85 <% html_title(l(:label_overview)) -%>
@@ -1,108 +1,108
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/mime_type'
3 require 'redmine/mime_type'
4 require 'redmine/themes'
4 require 'redmine/themes'
5 require 'redmine/plugin'
5 require 'redmine/plugin'
6
6
7 begin
7 begin
8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 rescue LoadError
9 rescue LoadError
10 # RMagick is not available
10 # RMagick is not available
11 end
11 end
12
12
13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar )
13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar )
14
14
15 # Permissions
15 # Permissions
16 Redmine::AccessControl.map do |map|
16 Redmine::AccessControl.map do |map|
17 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
17 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
18 map.permission :search_project, {:search => :index}, :public => true
18 map.permission :search_project, {:search => :index}, :public => true
19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23
23
24 map.project_module :issue_tracking do |map|
24 map.project_module :issue_tracking do |map|
25 # Issue categories
25 # Issue categories
26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 # Issues
27 # Issues
28 map.permission :view_issues, {:projects => [:changelog, :roadmap],
28 map.permission :view_issues, {:projects => [:changelog, :roadmap],
29 :issues => [:index, :changes, :show, :context_menu],
29 :issues => [:index, :changes, :show, :context_menu],
30 :versions => [:show, :status_by],
30 :versions => [:show, :status_by],
31 :queries => :index,
31 :queries => :index,
32 :reports => :issue_report}, :public => true
32 :reports => :issue_report}, :public => true
33 map.permission :add_issues, {:projects => :add_issue}
33 map.permission :add_issues, {:issues => :new}
34 map.permission :edit_issues, {:projects => :bulk_edit_issues,
34 map.permission :edit_issues, {:projects => :bulk_edit_issues,
35 :issues => [:edit, :update, :destroy_attachment]}
35 :issues => [:edit, :update, :destroy_attachment]}
36 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
36 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
37 map.permission :add_issue_notes, {:issues => :update}
37 map.permission :add_issue_notes, {:issues => :update}
38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
40 # Queries
40 # Queries
41 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
41 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
43 # Gantt & calendar
43 # Gantt & calendar
44 map.permission :view_gantt, :projects => :gantt
44 map.permission :view_gantt, :projects => :gantt
45 map.permission :view_calendar, :projects => :calendar
45 map.permission :view_calendar, :projects => :calendar
46 end
46 end
47
47
48 map.project_module :time_tracking do |map|
48 map.project_module :time_tracking do |map|
49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
50 map.permission :view_time_entries, :timelog => [:details, :report]
50 map.permission :view_time_entries, :timelog => [:details, :report]
51 end
51 end
52
52
53 map.project_module :news do |map|
53 map.project_module :news do |map|
54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
55 map.permission :view_news, {:news => [:index, :show]}, :public => true
55 map.permission :view_news, {:news => [:index, :show]}, :public => true
56 map.permission :comment_news, {:news => :add_comment}
56 map.permission :comment_news, {:news => :add_comment}
57 end
57 end
58
58
59 map.project_module :documents do |map|
59 map.project_module :documents do |map|
60 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
60 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
61 map.permission :view_documents, :documents => [:index, :show, :download]
61 map.permission :view_documents, :documents => [:index, :show, :download]
62 end
62 end
63
63
64 map.project_module :files do |map|
64 map.project_module :files do |map|
65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
66 map.permission :view_files, :projects => :list_files, :versions => :download
66 map.permission :view_files, :projects => :list_files, :versions => :download
67 end
67 end
68
68
69 map.project_module :wiki do |map|
69 map.project_module :wiki do |map|
70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
75 end
75 end
76
76
77 map.project_module :repository do |map|
77 map.project_module :repository do |map|
78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
81 end
81 end
82
82
83 map.project_module :boards do |map|
83 map.project_module :boards do |map|
84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
86 map.permission :add_messages, {:messages => [:new, :reply]}
86 map.permission :add_messages, {:messages => [:new, :reply]}
87 map.permission :edit_messages, {:messages => :edit}, :require => :member
87 map.permission :edit_messages, {:messages => :edit}, :require => :member
88 map.permission :delete_messages, {:messages => :destroy}, :require => :member
88 map.permission :delete_messages, {:messages => :destroy}, :require => :member
89 end
89 end
90 end
90 end
91
91
92 # Project menu configuration
92 # Project menu configuration
93 Redmine::MenuManager.map :project_menu do |menu|
93 Redmine::MenuManager.map :project_menu do |menu|
94 menu.push :overview, { :controller => 'projects', :action => 'show' }, :caption => :label_overview
94 menu.push :overview, { :controller => 'projects', :action => 'show' }, :caption => :label_overview
95 menu.push :activity, { :controller => 'projects', :action => 'activity' }, :caption => :label_activity
95 menu.push :activity, { :controller => 'projects', :action => 'activity' }, :caption => :label_activity
96 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' }, :caption => :label_roadmap
96 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' }, :caption => :label_roadmap
97 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
97 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
98 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
98 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
99 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
99 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
100 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
100 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
101 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }, :caption => :label_wiki
101 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }, :caption => :label_wiki
102 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
102 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
103 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
103 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
104 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
104 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
105 menu.push :repository, { :controller => 'repositories', :action => 'show' },
105 menu.push :repository, { :controller => 'repositories', :action => 'show' },
106 :if => Proc.new { |p| p.repository && !p.repository.new_record? }, :caption => :label_repository
106 :if => Proc.new { |p| p.repository && !p.repository.new_record? }, :caption => :label_repository
107 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :caption => :label_settings
107 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :caption => :label_settings
108 end
108 end
@@ -1,279 +1,329
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 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 < Test::Unit::TestCase
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :issues,
29 :issues,
30 :issue_statuses,
30 :issue_statuses,
31 :trackers,
31 :trackers,
32 :projects_trackers,
32 :issue_categories,
33 :issue_categories,
33 :enabled_modules,
34 :enabled_modules,
34 :enumerations,
35 :enumerations,
35 :attachments,
36 :attachments,
36 :workflows
37 :workflows
37
38
38 def setup
39 def setup
39 @controller = IssuesController.new
40 @controller = IssuesController.new
40 @request = ActionController::TestRequest.new
41 @request = ActionController::TestRequest.new
41 @response = ActionController::TestResponse.new
42 @response = ActionController::TestResponse.new
42 User.current = nil
43 User.current = nil
43 end
44 end
44
45
45 def test_index
46 def test_index
46 get :index
47 get :index
47 assert_response :success
48 assert_response :success
48 assert_template 'index.rhtml'
49 assert_template 'index.rhtml'
49 assert_not_nil assigns(:issues)
50 assert_not_nil assigns(:issues)
50 assert_nil assigns(:project)
51 assert_nil assigns(:project)
51 end
52 end
52
53
53 def test_index_with_project
54 def test_index_with_project
54 get :index, :project_id => 1
55 get :index, :project_id => 1
55 assert_response :success
56 assert_response :success
56 assert_template 'index.rhtml'
57 assert_template 'index.rhtml'
57 assert_not_nil assigns(:issues)
58 assert_not_nil assigns(:issues)
58 end
59 end
59
60
60 def test_index_with_project_and_filter
61 def test_index_with_project_and_filter
61 get :index, :project_id => 1, :set_filter => 1
62 get :index, :project_id => 1, :set_filter => 1
62 assert_response :success
63 assert_response :success
63 assert_template 'index.rhtml'
64 assert_template 'index.rhtml'
64 assert_not_nil assigns(:issues)
65 assert_not_nil assigns(:issues)
65 end
66 end
66
67
67 def test_index_csv_with_project
68 def test_index_csv_with_project
68 get :index, :format => 'csv'
69 get :index, :format => 'csv'
69 assert_response :success
70 assert_response :success
70 assert_not_nil assigns(:issues)
71 assert_not_nil assigns(:issues)
71 assert_equal 'text/csv', @response.content_type
72 assert_equal 'text/csv', @response.content_type
72
73
73 get :index, :project_id => 1, :format => 'csv'
74 get :index, :project_id => 1, :format => 'csv'
74 assert_response :success
75 assert_response :success
75 assert_not_nil assigns(:issues)
76 assert_not_nil assigns(:issues)
76 assert_equal 'text/csv', @response.content_type
77 assert_equal 'text/csv', @response.content_type
77 end
78 end
78
79
79 def test_index_pdf
80 def test_index_pdf
80 get :index, :format => 'pdf'
81 get :index, :format => 'pdf'
81 assert_response :success
82 assert_response :success
82 assert_not_nil assigns(:issues)
83 assert_not_nil assigns(:issues)
83 assert_equal 'application/pdf', @response.content_type
84 assert_equal 'application/pdf', @response.content_type
84
85
85 get :index, :project_id => 1, :format => 'pdf'
86 get :index, :project_id => 1, :format => 'pdf'
86 assert_response :success
87 assert_response :success
87 assert_not_nil assigns(:issues)
88 assert_not_nil assigns(:issues)
88 assert_equal 'application/pdf', @response.content_type
89 assert_equal 'application/pdf', @response.content_type
89 end
90 end
90
91
91 def test_changes
92 def test_changes
92 get :changes, :project_id => 1
93 get :changes, :project_id => 1
93 assert_response :success
94 assert_response :success
94 assert_not_nil assigns(:changes)
95 assert_not_nil assigns(:changes)
95 assert_equal 'application/atom+xml', @response.content_type
96 assert_equal 'application/atom+xml', @response.content_type
96 end
97 end
97
98
98 def test_show_by_anonymous
99 def test_show_by_anonymous
99 get :show, :id => 1
100 get :show, :id => 1
100 assert_response :success
101 assert_response :success
101 assert_template 'show.rhtml'
102 assert_template 'show.rhtml'
102 assert_not_nil assigns(:issue)
103 assert_not_nil assigns(:issue)
103 assert_equal Issue.find(1), assigns(:issue)
104 assert_equal Issue.find(1), assigns(:issue)
104
105
105 # anonymous role is allowed to add a note
106 # anonymous role is allowed to add a note
106 assert_tag :tag => 'form',
107 assert_tag :tag => 'form',
107 :descendant => { :tag => 'fieldset',
108 :descendant => { :tag => 'fieldset',
108 :child => { :tag => 'legend',
109 :child => { :tag => 'legend',
109 :content => /Notes/ } }
110 :content => /Notes/ } }
110 end
111 end
111
112
112 def test_show_by_manager
113 def test_show_by_manager
113 @request.session[:user_id] = 2
114 @request.session[:user_id] = 2
114 get :show, :id => 1
115 get :show, :id => 1
115 assert_response :success
116 assert_response :success
116
117
117 assert_tag :tag => 'form',
118 assert_tag :tag => 'form',
118 :descendant => { :tag => 'fieldset',
119 :descendant => { :tag => 'fieldset',
119 :child => { :tag => 'legend',
120 :child => { :tag => 'legend',
120 :content => /Change properties/ } },
121 :content => /Change properties/ } },
121 :descendant => { :tag => 'fieldset',
122 :descendant => { :tag => 'fieldset',
122 :child => { :tag => 'legend',
123 :child => { :tag => 'legend',
123 :content => /Log time/ } },
124 :content => /Log time/ } },
124 :descendant => { :tag => 'fieldset',
125 :descendant => { :tag => 'fieldset',
125 :child => { :tag => 'legend',
126 :child => { :tag => 'legend',
126 :content => /Notes/ } }
127 :content => /Notes/ } }
127 end
128 end
128
129
130 def test_get_new
131 @request.session[:user_id] = 2
132 get :new, :project_id => 1, :tracker_id => 1
133 assert_response :success
134 assert_template 'new'
135 end
136
137 def test_get_new_without_tracker_id
138 @request.session[:user_id] = 2
139 get :new, :project_id => 1
140 assert_response :success
141 assert_template 'new'
142
143 issue = assigns(:issue)
144 assert_not_nil issue
145 assert_equal Project.find(1).trackers.first, issue.tracker
146 end
147
148 def test_update_new_form
149 @request.session[:user_id] = 2
150 xhr :post, :new, :project_id => 1,
151 :issue => {:tracker_id => 2,
152 :subject => 'This is the test_new issue',
153 :description => 'This is the description',
154 :priority_id => 5}
155 assert_response :success
156 assert_template 'new'
157 end
158
159 def test_post_new
160 @request.session[:user_id] = 2
161 post :new, :project_id => 1,
162 :issue => {:tracker_id => 1,
163 :subject => 'This is the test_new issue',
164 :description => 'This is the description',
165 :priority_id => 5}
166 assert_redirected_to 'projects/ecookbook/issues'
167 assert Issue.find_by_subject('This is the test_new issue')
168 end
169
170 def test_copy_issue
171 @request.session[:user_id] = 2
172 get :new, :project_id => 1, :copy_from => 1
173 assert_template 'new'
174 assert_not_nil assigns(:issue)
175 orig = Issue.find(1)
176 assert_equal orig.subject, assigns(:issue).subject
177 end
178
129 def test_get_edit
179 def test_get_edit
130 @request.session[:user_id] = 2
180 @request.session[:user_id] = 2
131 get :edit, :id => 1
181 get :edit, :id => 1
132 assert_response :success
182 assert_response :success
133 assert_template 'edit'
183 assert_template 'edit'
134 assert_not_nil assigns(:issue)
184 assert_not_nil assigns(:issue)
135 assert_equal Issue.find(1), assigns(:issue)
185 assert_equal Issue.find(1), assigns(:issue)
136 end
186 end
137
187
138 def test_post_edit
188 def test_post_edit
139 @request.session[:user_id] = 2
189 @request.session[:user_id] = 2
140 ActionMailer::Base.deliveries.clear
190 ActionMailer::Base.deliveries.clear
141
191
142 issue = Issue.find(1)
192 issue = Issue.find(1)
143 old_subject = issue.subject
193 old_subject = issue.subject
144 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
194 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
145
195
146 post :edit, :id => 1, :issue => {:subject => new_subject}
196 post :edit, :id => 1, :issue => {:subject => new_subject}
147 assert_redirected_to 'issues/show/1'
197 assert_redirected_to 'issues/show/1'
148 issue.reload
198 issue.reload
149 assert_equal new_subject, issue.subject
199 assert_equal new_subject, issue.subject
150
200
151 mail = ActionMailer::Base.deliveries.last
201 mail = ActionMailer::Base.deliveries.last
152 assert_kind_of TMail::Mail, mail
202 assert_kind_of TMail::Mail, mail
153 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
203 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
154 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
204 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
155 end
205 end
156
206
157 def test_get_update
207 def test_get_update
158 @request.session[:user_id] = 2
208 @request.session[:user_id] = 2
159 get :update, :id => 1
209 get :update, :id => 1
160 assert_response :success
210 assert_response :success
161 assert_template 'update'
211 assert_template 'update'
162 end
212 end
163
213
164 def test_update_with_status_and_assignee_change
214 def test_update_with_status_and_assignee_change
165 issue = Issue.find(1)
215 issue = Issue.find(1)
166 assert_equal 1, issue.status_id
216 assert_equal 1, issue.status_id
167 @request.session[:user_id] = 2
217 @request.session[:user_id] = 2
168 post :update,
218 post :update,
169 :id => 1,
219 :id => 1,
170 :issue => { :status_id => 2, :assigned_to_id => 3 },
220 :issue => { :status_id => 2, :assigned_to_id => 3 },
171 :notes => 'Assigned to dlopper'
221 :notes => 'Assigned to dlopper'
172 assert_redirected_to 'issues/show/1'
222 assert_redirected_to 'issues/show/1'
173 issue.reload
223 issue.reload
174 assert_equal 2, issue.status_id
224 assert_equal 2, issue.status_id
175 j = issue.journals.find(:first, :order => 'id DESC')
225 j = issue.journals.find(:first, :order => 'id DESC')
176 assert_equal 'Assigned to dlopper', j.notes
226 assert_equal 'Assigned to dlopper', j.notes
177 assert_equal 2, j.details.size
227 assert_equal 2, j.details.size
178
228
179 mail = ActionMailer::Base.deliveries.last
229 mail = ActionMailer::Base.deliveries.last
180 assert mail.body.include?("Status changed from New to Assigned")
230 assert mail.body.include?("Status changed from New to Assigned")
181 end
231 end
182
232
183 def test_update_with_note_only
233 def test_update_with_note_only
184 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
234 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
185 # anonymous user
235 # anonymous user
186 post :update,
236 post :update,
187 :id => 1,
237 :id => 1,
188 :notes => notes
238 :notes => notes
189 assert_redirected_to 'issues/show/1'
239 assert_redirected_to 'issues/show/1'
190 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
240 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
191 assert_equal notes, j.notes
241 assert_equal notes, j.notes
192 assert_equal 0, j.details.size
242 assert_equal 0, j.details.size
193 assert_equal User.anonymous, j.user
243 assert_equal User.anonymous, j.user
194
244
195 mail = ActionMailer::Base.deliveries.last
245 mail = ActionMailer::Base.deliveries.last
196 assert mail.body.include?(notes)
246 assert mail.body.include?(notes)
197 end
247 end
198
248
199 def test_update_with_note_and_spent_time
249 def test_update_with_note_and_spent_time
200 @request.session[:user_id] = 2
250 @request.session[:user_id] = 2
201 spent_hours_before = Issue.find(1).spent_hours
251 spent_hours_before = Issue.find(1).spent_hours
202 post :update,
252 post :update,
203 :id => 1,
253 :id => 1,
204 :notes => '2.5 hours added',
254 :notes => '2.5 hours added',
205 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
255 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
206 assert_redirected_to 'issues/show/1'
256 assert_redirected_to 'issues/show/1'
207
257
208 issue = Issue.find(1)
258 issue = Issue.find(1)
209
259
210 j = issue.journals.find(:first, :order => 'id DESC')
260 j = issue.journals.find(:first, :order => 'id DESC')
211 assert_equal '2.5 hours added', j.notes
261 assert_equal '2.5 hours added', j.notes
212 assert_equal 0, j.details.size
262 assert_equal 0, j.details.size
213
263
214 t = issue.time_entries.find(:first, :order => 'id DESC')
264 t = issue.time_entries.find(:first, :order => 'id DESC')
215 assert_not_nil t
265 assert_not_nil t
216 assert_equal 2.5, t.hours
266 assert_equal 2.5, t.hours
217 assert_equal spent_hours_before + 2.5, issue.spent_hours
267 assert_equal spent_hours_before + 2.5, issue.spent_hours
218 end
268 end
219
269
220 def test_update_with_attachment_only
270 def test_update_with_attachment_only
221 # anonymous user
271 # anonymous user
222 post :update,
272 post :update,
223 :id => 1,
273 :id => 1,
224 :notes => '',
274 :notes => '',
225 :attachments => [ test_uploaded_file('testfile.txt', 'text/plain') ]
275 :attachments => [ test_uploaded_file('testfile.txt', 'text/plain') ]
226 assert_redirected_to 'issues/show/1'
276 assert_redirected_to 'issues/show/1'
227 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
277 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
228 assert j.notes.blank?
278 assert j.notes.blank?
229 assert_equal 1, j.details.size
279 assert_equal 1, j.details.size
230 assert_equal 'testfile.txt', j.details.first.value
280 assert_equal 'testfile.txt', j.details.first.value
231 assert_equal User.anonymous, j.user
281 assert_equal User.anonymous, j.user
232
282
233 mail = ActionMailer::Base.deliveries.last
283 mail = ActionMailer::Base.deliveries.last
234 assert mail.body.include?('testfile.txt')
284 assert mail.body.include?('testfile.txt')
235 end
285 end
236
286
237 def test_update_with_no_change
287 def test_update_with_no_change
238 issue = Issue.find(1)
288 issue = Issue.find(1)
239 issue.journals.clear
289 issue.journals.clear
240 ActionMailer::Base.deliveries.clear
290 ActionMailer::Base.deliveries.clear
241
291
242 post :update,
292 post :update,
243 :id => 1,
293 :id => 1,
244 :notes => ''
294 :notes => ''
245 assert_redirected_to 'issues/show/1'
295 assert_redirected_to 'issues/show/1'
246
296
247 issue.reload
297 issue.reload
248 assert issue.journals.empty?
298 assert issue.journals.empty?
249 # No email should be sent
299 # No email should be sent
250 assert ActionMailer::Base.deliveries.empty?
300 assert ActionMailer::Base.deliveries.empty?
251 end
301 end
252
302
253 def test_context_menu
303 def test_context_menu
254 @request.session[:user_id] = 2
304 @request.session[:user_id] = 2
255 get :context_menu, :id => 1
305 get :context_menu, :id => 1
256 assert_response :success
306 assert_response :success
257 assert_template 'context_menu'
307 assert_template 'context_menu'
258 end
308 end
259
309
260 def test_destroy
310 def test_destroy
261 @request.session[:user_id] = 2
311 @request.session[:user_id] = 2
262 post :destroy, :id => 1
312 post :destroy, :id => 1
263 assert_redirected_to 'projects/ecookbook/issues'
313 assert_redirected_to 'projects/ecookbook/issues'
264 assert_nil Issue.find_by_id(1)
314 assert_nil Issue.find_by_id(1)
265 end
315 end
266
316
267 def test_destroy_attachment
317 def test_destroy_attachment
268 issue = Issue.find(3)
318 issue = Issue.find(3)
269 a = issue.attachments.size
319 a = issue.attachments.size
270 @request.session[:user_id] = 2
320 @request.session[:user_id] = 2
271 post :destroy_attachment, :id => 3, :attachment_id => 1
321 post :destroy_attachment, :id => 3, :attachment_id => 1
272 assert_redirected_to 'issues/show/3'
322 assert_redirected_to 'issues/show/3'
273 assert_nil Attachment.find_by_id(1)
323 assert_nil Attachment.find_by_id(1)
274 issue.reload
324 issue.reload
275 assert_equal((a-1), issue.attachments.size)
325 assert_equal((a-1), issue.attachments.size)
276 j = issue.journals.find(:first, :order => 'created_on DESC')
326 j = issue.journals.find(:first, :order => 'created_on DESC')
277 assert_equal 'attachment', j.details.first.property
327 assert_equal 'attachment', j.details.first.property
278 end
328 end
279 end
329 end
@@ -1,258 +1,239
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 File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < Test::Unit::TestCase
24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations
26
26
27 def setup
27 def setup
28 @controller = ProjectsController.new
28 @controller = ProjectsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 end
31 end
32
32
33 def test_index
33 def test_index
34 get :index
34 get :index
35 assert_response :success
35 assert_response :success
36 assert_template 'list'
36 assert_template 'list'
37 end
37 end
38
38
39 def test_list
39 def test_list
40 get :list
40 get :list
41 assert_response :success
41 assert_response :success
42 assert_template 'list'
42 assert_template 'list'
43 assert_not_nil assigns(:project_tree)
43 assert_not_nil assigns(:project_tree)
44 # Root project as hash key
44 # Root project as hash key
45 assert assigns(:project_tree).has_key?(Project.find(1))
45 assert assigns(:project_tree).has_key?(Project.find(1))
46 # Subproject in corresponding value
46 # Subproject in corresponding value
47 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
47 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
48 end
48 end
49
49
50 def test_show_by_id
50 def test_show_by_id
51 get :show, :id => 1
51 get :show, :id => 1
52 assert_response :success
52 assert_response :success
53 assert_template 'show'
53 assert_template 'show'
54 assert_not_nil assigns(:project)
54 assert_not_nil assigns(:project)
55 end
55 end
56
56
57 def test_show_by_identifier
57 def test_show_by_identifier
58 get :show, :id => 'ecookbook'
58 get :show, :id => 'ecookbook'
59 assert_response :success
59 assert_response :success
60 assert_template 'show'
60 assert_template 'show'
61 assert_not_nil assigns(:project)
61 assert_not_nil assigns(:project)
62 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
62 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
63 end
63 end
64
64
65 def test_settings
65 def test_settings
66 @request.session[:user_id] = 2 # manager
66 @request.session[:user_id] = 2 # manager
67 get :settings, :id => 1
67 get :settings, :id => 1
68 assert_response :success
68 assert_response :success
69 assert_template 'settings'
69 assert_template 'settings'
70 end
70 end
71
71
72 def test_edit
72 def test_edit
73 @request.session[:user_id] = 2 # manager
73 @request.session[:user_id] = 2 # manager
74 post :edit, :id => 1, :project => {:name => 'Test changed name'}
74 post :edit, :id => 1, :project => {:name => 'Test changed name'}
75 assert_redirected_to 'projects/settings/ecookbook'
75 assert_redirected_to 'projects/settings/ecookbook'
76 project = Project.find(1)
76 project = Project.find(1)
77 assert_equal 'Test changed name', project.name
77 assert_equal 'Test changed name', project.name
78 end
78 end
79
79
80 def test_get_destroy
80 def test_get_destroy
81 @request.session[:user_id] = 1 # admin
81 @request.session[:user_id] = 1 # admin
82 get :destroy, :id => 1
82 get :destroy, :id => 1
83 assert_response :success
83 assert_response :success
84 assert_template 'destroy'
84 assert_template 'destroy'
85 assert_not_nil Project.find_by_id(1)
85 assert_not_nil Project.find_by_id(1)
86 end
86 end
87
87
88 def test_post_destroy
88 def test_post_destroy
89 @request.session[:user_id] = 1 # admin
89 @request.session[:user_id] = 1 # admin
90 post :destroy, :id => 1, :confirm => 1
90 post :destroy, :id => 1, :confirm => 1
91 assert_redirected_to 'admin/projects'
91 assert_redirected_to 'admin/projects'
92 assert_nil Project.find_by_id(1)
92 assert_nil Project.find_by_id(1)
93 end
93 end
94
94
95 def test_bulk_edit_issues
95 def test_bulk_edit_issues
96 @request.session[:user_id] = 2
96 @request.session[:user_id] = 2
97 # update issues priority
97 # update issues priority
98 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
98 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
99 assert_response 302
99 assert_response 302
100 # check that the issues were updated
100 # check that the issues were updated
101 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
101 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
102 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
102 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
103 end
103 end
104
104
105 def test_move_issues_to_another_project
105 def test_move_issues_to_another_project
106 @request.session[:user_id] = 1
106 @request.session[:user_id] = 1
107 post :move_issues, :id => 1, :issue_ids => [1, 2], :new_project_id => 2
107 post :move_issues, :id => 1, :issue_ids => [1, 2], :new_project_id => 2
108 assert_redirected_to 'projects/ecookbook/issues'
108 assert_redirected_to 'projects/ecookbook/issues'
109 assert_equal 2, Issue.find(1).project_id
109 assert_equal 2, Issue.find(1).project_id
110 assert_equal 2, Issue.find(2).project_id
110 assert_equal 2, Issue.find(2).project_id
111 end
111 end
112
112
113 def test_move_issues_to_another_tracker
113 def test_move_issues_to_another_tracker
114 @request.session[:user_id] = 1
114 @request.session[:user_id] = 1
115 post :move_issues, :id => 1, :issue_ids => [1, 2], :new_tracker_id => 2
115 post :move_issues, :id => 1, :issue_ids => [1, 2], :new_tracker_id => 2
116 assert_redirected_to 'projects/ecookbook/issues'
116 assert_redirected_to 'projects/ecookbook/issues'
117 assert_equal 2, Issue.find(1).tracker_id
117 assert_equal 2, Issue.find(1).tracker_id
118 assert_equal 2, Issue.find(2).tracker_id
118 assert_equal 2, Issue.find(2).tracker_id
119 end
119 end
120
120
121 def test_list_files
121 def test_list_files
122 get :list_files, :id => 1
122 get :list_files, :id => 1
123 assert_response :success
123 assert_response :success
124 assert_template 'list_files'
124 assert_template 'list_files'
125 assert_not_nil assigns(:versions)
125 assert_not_nil assigns(:versions)
126 end
126 end
127
127
128 def test_changelog
128 def test_changelog
129 get :changelog, :id => 1
129 get :changelog, :id => 1
130 assert_response :success
130 assert_response :success
131 assert_template 'changelog'
131 assert_template 'changelog'
132 assert_not_nil assigns(:versions)
132 assert_not_nil assigns(:versions)
133 end
133 end
134
134
135 def test_roadmap
135 def test_roadmap
136 get :roadmap, :id => 1
136 get :roadmap, :id => 1
137 assert_response :success
137 assert_response :success
138 assert_template 'roadmap'
138 assert_template 'roadmap'
139 assert_not_nil assigns(:versions)
139 assert_not_nil assigns(:versions)
140 # Version with no date set appears
140 # Version with no date set appears
141 assert assigns(:versions).include?(Version.find(3))
141 assert assigns(:versions).include?(Version.find(3))
142 # Completed version doesn't appear
142 # Completed version doesn't appear
143 assert !assigns(:versions).include?(Version.find(1))
143 assert !assigns(:versions).include?(Version.find(1))
144 end
144 end
145
145
146 def test_roadmap_with_completed_versions
146 def test_roadmap_with_completed_versions
147 get :roadmap, :id => 1, :completed => 1
147 get :roadmap, :id => 1, :completed => 1
148 assert_response :success
148 assert_response :success
149 assert_template 'roadmap'
149 assert_template 'roadmap'
150 assert_not_nil assigns(:versions)
150 assert_not_nil assigns(:versions)
151 # Version with no date set appears
151 # Version with no date set appears
152 assert assigns(:versions).include?(Version.find(3))
152 assert assigns(:versions).include?(Version.find(3))
153 # Completed version appears
153 # Completed version appears
154 assert assigns(:versions).include?(Version.find(1))
154 assert assigns(:versions).include?(Version.find(1))
155 end
155 end
156
156
157 def test_activity
157 def test_activity
158 get :activity, :id => 1, :year => 2.days.ago.to_date.year, :month => 2.days.ago.to_date.month
158 get :activity, :id => 1, :year => 2.days.ago.to_date.year, :month => 2.days.ago.to_date.month
159 assert_response :success
159 assert_response :success
160 assert_template 'activity'
160 assert_template 'activity'
161 assert_not_nil assigns(:events_by_day)
161 assert_not_nil assigns(:events_by_day)
162
162
163 assert_tag :tag => "h3",
163 assert_tag :tag => "h3",
164 :content => /#{2.days.ago.to_date.day}/,
164 :content => /#{2.days.ago.to_date.day}/,
165 :sibling => { :tag => "ul",
165 :sibling => { :tag => "ul",
166 :child => { :tag => "li",
166 :child => { :tag => "li",
167 :child => { :tag => "p",
167 :child => { :tag => "p",
168 :content => /(#{IssueStatus.find(2).name})/,
168 :content => /(#{IssueStatus.find(2).name})/,
169 }
169 }
170 }
170 }
171 }
171 }
172
172
173 get :activity, :id => 1, :year => 3.days.ago.to_date.year, :month => 3.days.ago.to_date.month
173 get :activity, :id => 1, :year => 3.days.ago.to_date.year, :month => 3.days.ago.to_date.month
174 assert_response :success
174 assert_response :success
175 assert_template 'activity'
175 assert_template 'activity'
176 assert_not_nil assigns(:events_by_day)
176 assert_not_nil assigns(:events_by_day)
177
177
178 assert_tag :tag => "h3",
178 assert_tag :tag => "h3",
179 :content => /#{3.day.ago.to_date.day}/,
179 :content => /#{3.day.ago.to_date.day}/,
180 :sibling => { :tag => "ul",
180 :sibling => { :tag => "ul",
181 :child => { :tag => "li",
181 :child => { :tag => "li",
182 :child => { :tag => "p",
182 :child => { :tag => "p",
183 :content => /#{Issue.find(1).subject}/,
183 :content => /#{Issue.find(1).subject}/,
184 }
184 }
185 }
185 }
186 }
186 }
187 end
187 end
188
188
189 def test_calendar
189 def test_calendar
190 get :calendar, :id => 1
190 get :calendar, :id => 1
191 assert_response :success
191 assert_response :success
192 assert_template 'calendar'
192 assert_template 'calendar'
193 assert_not_nil assigns(:calendar)
193 assert_not_nil assigns(:calendar)
194 end
194 end
195
195
196 def test_calendar_with_subprojects
196 def test_calendar_with_subprojects
197 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
197 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
198 assert_response :success
198 assert_response :success
199 assert_template 'calendar'
199 assert_template 'calendar'
200 assert_not_nil assigns(:calendar)
200 assert_not_nil assigns(:calendar)
201 end
201 end
202
202
203 def test_gantt
203 def test_gantt
204 get :gantt, :id => 1
204 get :gantt, :id => 1
205 assert_response :success
205 assert_response :success
206 assert_template 'gantt.rhtml'
206 assert_template 'gantt.rhtml'
207 assert_not_nil assigns(:events)
207 assert_not_nil assigns(:events)
208 end
208 end
209
209
210 def test_gantt_with_subprojects
210 def test_gantt_with_subprojects
211 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
211 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
212 assert_response :success
212 assert_response :success
213 assert_template 'gantt.rhtml'
213 assert_template 'gantt.rhtml'
214 assert_not_nil assigns(:events)
214 assert_not_nil assigns(:events)
215 end
215 end
216
216
217 def test_gantt_export_to_pdf
217 def test_gantt_export_to_pdf
218 get :gantt, :id => 1, :format => 'pdf'
218 get :gantt, :id => 1, :format => 'pdf'
219 assert_response :success
219 assert_response :success
220 assert_template 'gantt.rfpdf'
220 assert_template 'gantt.rfpdf'
221 assert_equal 'application/pdf', @response.content_type
221 assert_equal 'application/pdf', @response.content_type
222 assert_not_nil assigns(:events)
222 assert_not_nil assigns(:events)
223 end
223 end
224
224
225 def test_archive
225 def test_archive
226 @request.session[:user_id] = 1 # admin
226 @request.session[:user_id] = 1 # admin
227 post :archive, :id => 1
227 post :archive, :id => 1
228 assert_redirected_to 'admin/projects'
228 assert_redirected_to 'admin/projects'
229 assert !Project.find(1).active?
229 assert !Project.find(1).active?
230 end
230 end
231
231
232 def test_unarchive
232 def test_unarchive
233 @request.session[:user_id] = 1 # admin
233 @request.session[:user_id] = 1 # admin
234 Project.find(1).archive
234 Project.find(1).archive
235 post :unarchive, :id => 1
235 post :unarchive, :id => 1
236 assert_redirected_to 'admin/projects'
236 assert_redirected_to 'admin/projects'
237 assert Project.find(1).active?
237 assert Project.find(1).active?
238 end
238 end
239
240 def test_add_issue
241 @request.session[:user_id] = 2
242 get :add_issue, :id => 1, :tracker_id => 1
243 assert_response :success
244 assert_template 'add_issue'
245 post :add_issue, :id => 1, :issue => {:tracker_id => 1, :subject => 'This is the test_add_issue issue', :description => 'This is the description', :priority_id => 5}
246 assert_redirected_to 'projects/ecookbook/issues'
247 assert Issue.find_by_subject('This is the test_add_issue issue')
248 end
249
250 def test_copy_issue
251 @request.session[:user_id] = 2
252 get :add_issue, :id => 1, :copy_from => 1
253 assert_template 'add_issue'
254 assert_not_nil assigns(:issue)
255 orig = Issue.find(1)
256 assert_equal orig.subject, assigns(:issue).subject
257 end
258 end
239 end
@@ -1,60 +1,60
1 require "#{File.dirname(__FILE__)}/../test_helper"
1 require "#{File.dirname(__FILE__)}/../test_helper"
2
2
3 class IssuesTest < ActionController::IntegrationTest
3 class IssuesTest < ActionController::IntegrationTest
4 fixtures :projects, :users, :trackers, :issue_statuses, :issues, :enumerations
4 fixtures :projects, :users, :trackers, :issue_statuses, :issues, :enumerations
5
5
6 # create an issue
6 # create an issue
7 def test_add_issue
7 def test_add_issue
8 log_user('jsmith', 'jsmith')
8 log_user('jsmith', 'jsmith')
9 get "projects/add_issue/1", :tracker_id => "1"
9 get 'projects/1/issues/new', :tracker_id => '1'
10 assert_response :success
10 assert_response :success
11 assert_template "projects/add_issue"
11 assert_template 'issues/new'
12
12
13 post "projects/add_issue/1", :tracker_id => "1",
13 post 'projects/1/issues/new', :tracker_id => "1",
14 :issue => { :start_date => "2006-12-26",
14 :issue => { :start_date => "2006-12-26",
15 :priority_id => "3",
15 :priority_id => "3",
16 :subject => "new test issue",
16 :subject => "new test issue",
17 :category_id => "",
17 :category_id => "",
18 :description => "new issue",
18 :description => "new issue",
19 :done_ratio => "0",
19 :done_ratio => "0",
20 :due_date => "",
20 :due_date => "",
21 :assigned_to_id => "" }
21 :assigned_to_id => "" }
22 # find created issue
22 # find created issue
23 issue = Issue.find_by_subject("new test issue")
23 issue = Issue.find_by_subject("new test issue")
24 assert_kind_of Issue, issue
24 assert_kind_of Issue, issue
25
25
26 # check redirection
26 # check redirection
27 assert_redirected_to "projects/ecookbook/issues"
27 assert_redirected_to "projects/ecookbook/issues"
28 follow_redirect!
28 follow_redirect!
29 assert assigns(:issues).include?(issue)
29 assert assigns(:issues).include?(issue)
30
30
31 # check issue attributes
31 # check issue attributes
32 assert_equal 'jsmith', issue.author.login
32 assert_equal 'jsmith', issue.author.login
33 assert_equal 1, issue.project.id
33 assert_equal 1, issue.project.id
34 assert_equal 1, issue.status.id
34 assert_equal 1, issue.status.id
35 end
35 end
36
36
37 # add then remove 2 attachments to an issue
37 # add then remove 2 attachments to an issue
38 def test_issue_attachements
38 def test_issue_attachements
39 log_user('jsmith', 'jsmith')
39 log_user('jsmith', 'jsmith')
40
40
41 post 'issues/update/1',
41 post 'issues/update/1',
42 :notes => 'Some notes',
42 :notes => 'Some notes',
43 :attachments => ([] << ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/testfile.txt', 'text/plain'))
43 :attachments => ([] << ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/testfile.txt', 'text/plain'))
44 assert_redirected_to "issues/show/1"
44 assert_redirected_to "issues/show/1"
45
45
46 # make sure attachment was saved
46 # make sure attachment was saved
47 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
47 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
48 assert_kind_of Attachment, attachment
48 assert_kind_of Attachment, attachment
49 assert_equal Issue.find(1), attachment.container
49 assert_equal Issue.find(1), attachment.container
50 # verify the size of the attachment stored in db
50 # verify the size of the attachment stored in db
51 #assert_equal file_data_1.length, attachment.filesize
51 #assert_equal file_data_1.length, attachment.filesize
52 # verify that the attachment was written to disk
52 # verify that the attachment was written to disk
53 assert File.exist?(attachment.diskfile)
53 assert File.exist?(attachment.diskfile)
54
54
55 # remove the attachments
55 # remove the attachments
56 Issue.find(1).attachments.each(&:destroy)
56 Issue.find(1).attachments.each(&:destroy)
57 assert_equal 0, Issue.find(1).attachments.length
57 assert_equal 0, Issue.find(1).attachments.length
58 end
58 end
59
59
60 end
60 end
General Comments 0
You need to be logged in to leave comments. Login now