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