##// END OF EJS Templates
ProjectsController#list_issues, #export_issues_csv and #export_issues_pdf merged into IssuesController#index...
Jean-Philippe Lang -
r874:8509cf80f009
parent child
Show More
@@ -0,0 +1,30
1 xml.instruct!
2 xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
3 xml.title @title
4 xml.link "rel" => "self", "href" => url_for(:controller => 'feeds', :action => 'history', :format => 'atom', :only_path => false)
5 xml.link "rel" => "alternate", "href" => url_for(:controller => 'welcome', :only_path => false)
6 xml.id url_for(:controller => 'welcome', :only_path => false)
7 xml.updated((@changes.first ? @changes.first.event_datetime : Time.now).xmlschema)
8 xml.author { xml.name "#{Setting.app_title}" }
9 @changes.each do |change|
10 issue = change.issue
11 xml.entry do
12 xml.title "#{issue.project.name} - #{issue.tracker.name} ##{issue.id}: #{issue.subject}"
13 xml.link "rel" => "alternate", "href" => url_for(:controller => 'issues' , :action => 'show', :id => issue, :only_path => false)
14 xml.id url_for(:controller => 'issues' , :action => 'show', :id => issue, :journal_id => change, :only_path => false)
15 xml.updated change.created_on.xmlschema
16 xml.author do
17 xml.name change.user.name
18 xml.email(change.user.mail)
19 end
20 xml.content "type" => "html" do
21 xml.text! '<ul>'
22 change.details.each do |detail|
23 xml.text! '<li>' + show_detail(detail, false) + '</li>'
24 end
25 xml.text! '</ul>'
26 xml.text! textilizable(change.notes) unless change.notes.blank?
27 end
28 end
29 end
30 end No newline at end of file
@@ -0,0 +1,86
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
20
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
23
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :issues, :enabled_modules, :enumerations
26
27 def setup
28 @controller = IssuesController.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
32 end
33
34 def test_index
35 get :index
36 assert_response :success
37 assert_template 'index.rhtml'
38 assert_not_nil assigns(:issues)
39 assert_nil assigns(:project)
40 end
41
42 def test_index_with_project
43 get :index, :project_id => 1
44 assert_response :success
45 assert_template 'index.rhtml'
46 assert_not_nil assigns(:issues)
47 end
48
49 def test_index_with_project_and_filter
50 get :index, :project_id => 1, :set_filter => 1
51 assert_response :success
52 assert_template 'index.rhtml'
53 assert_not_nil assigns(:issues)
54 end
55
56 def test_index_csv_with_project
57 get :index, :format => 'csv'
58 assert_response :success
59 assert_not_nil assigns(:issues)
60 assert_equal 'text/csv', @response.content_type
61
62 get :index, :project_id => 1, :format => 'csv'
63 assert_response :success
64 assert_not_nil assigns(:issues)
65 assert_equal 'text/csv', @response.content_type
66 end
67
68 def test_index_pdf
69 get :index, :format => 'pdf'
70 assert_response :success
71 assert_not_nil assigns(:issues)
72 assert_equal 'application/pdf', @response.content_type
73
74 get :index, :project_id => 1, :format => 'pdf'
75 assert_response :success
76 assert_not_nil assigns(:issues)
77 assert_equal 'application/pdf', @response.content_type
78 end
79
80 def test_changes
81 get :changes, :project_id => 1
82 assert_response :success
83 assert_not_nil assigns(:changes)
84 assert_equal 'application/atom+xml', @response.content_type
85 end
86 end
@@ -1,217 +1,250
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 layout 'base', :except => :export_pdf
19 layout 'base'
20 before_filter :find_project, :authorize, :except => [:index, :preview]
20 before_filter :find_project, :authorize, :except => [:index, :changes, :preview]
21 accept_key_auth :index
21 before_filter :find_optional_project, :only => [:index, :changes]
22 accept_key_auth :index, :changes
22
23
23 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
24 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
24
25
25 helper :projects
26 helper :projects
26 include ProjectsHelper
27 include ProjectsHelper
27 helper :custom_fields
28 helper :custom_fields
28 include CustomFieldsHelper
29 include CustomFieldsHelper
29 helper :ifpdf
30 helper :ifpdf
30 include IfpdfHelper
31 include IfpdfHelper
31 helper :issue_relations
32 helper :issue_relations
32 include IssueRelationsHelper
33 include IssueRelationsHelper
33 helper :watchers
34 helper :watchers
34 include WatchersHelper
35 include WatchersHelper
35 helper :attachments
36 helper :attachments
36 include AttachmentsHelper
37 include AttachmentsHelper
37 helper :queries
38 helper :queries
38 helper :sort
39 helper :sort
39 include SortHelper
40 include SortHelper
41 include IssuesHelper
40
42
41 def index
43 def index
42 sort_init "#{Issue.table_name}.id", "desc"
44 sort_init "#{Issue.table_name}.id", "desc"
43 sort_update
45 sort_update
44 retrieve_query
46 retrieve_query
45 if @query.valid?
47 if @query.valid?
48 limit = %w(pdf csv).include?(params[:format]) ? Setting.issues_export_limit.to_i : 25
46 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
49 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
47 @issue_pages = Paginator.new self, @issue_count, 25, params['page']
50 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
48 @issues = Issue.find :all, :order => sort_clause,
51 @issues = Issue.find :all, :order => sort_clause,
49 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
52 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
50 :conditions => @query.statement,
53 :conditions => @query.statement,
51 :limit => @issue_pages.items_per_page,
54 :limit => limit,
52 :offset => @issue_pages.current.offset
55 :offset => @issue_pages.current.offset
53 end
54 respond_to do |format|
56 respond_to do |format|
55 format.html { render :layout => false if request.xhr? }
57 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
56 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
58 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
59 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
60 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
61 end
62 else
63 # Send html if the query is not valid
64 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
65 end
66 end
67
68 def changes
69 sort_init "#{Issue.table_name}.id", "desc"
70 sort_update
71 retrieve_query
72 if @query.valid?
73 @changes = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
74 :conditions => @query.statement,
75 :limit => 25,
76 :order => "#{Journal.table_name}.created_on DESC"
57 end
77 end
78 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
79 render :layout => false, :content_type => 'application/atom+xml'
58 end
80 end
59
81
60 def show
82 def show
61 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
83 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
62 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
84 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
63
64 if params[:format]=='pdf'
65 @options_for_rfpdf ||= {}
66 @options_for_rfpdf[:file_name] = "#{@project.identifier}-#{@issue.id}.pdf"
67 render :template => 'issues/show.rfpdf', :layout => false
68 else
69 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
85 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
70 render :template => 'issues/show.rhtml'
86 respond_to do |format|
87 format.html { render :template => 'issues/show.rhtml' }
88 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
71 end
89 end
72 end
90 end
73
91
74 def edit
92 def edit
75 @priorities = Enumeration::get_values('IPRI')
93 @priorities = Enumeration::get_values('IPRI')
76 if request.get?
94 if request.get?
77 @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) }
95 @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) }
78 else
96 else
79 begin
97 begin
80 @issue.init_journal(self.logged_in_user)
98 @issue.init_journal(self.logged_in_user)
81 # Retrieve custom fields and values
99 # Retrieve custom fields and values
82 if params["custom_fields"]
100 if params["custom_fields"]
83 @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]) }
101 @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]) }
84 @issue.custom_values = @custom_values
102 @issue.custom_values = @custom_values
85 end
103 end
86 @issue.attributes = params[:issue]
104 @issue.attributes = params[:issue]
87 if @issue.save
105 if @issue.save
88 flash[:notice] = l(:notice_successful_update)
106 flash[:notice] = l(:notice_successful_update)
89 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
107 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
90 end
108 end
91 rescue ActiveRecord::StaleObjectError
109 rescue ActiveRecord::StaleObjectError
92 # Optimistic locking exception
110 # Optimistic locking exception
93 flash[:error] = l(:notice_locking_conflict)
111 flash[:error] = l(:notice_locking_conflict)
94 end
112 end
95 end
113 end
96 end
114 end
97
115
98 def add_note
116 def add_note
99 journal = @issue.init_journal(User.current, params[:notes])
117 journal = @issue.init_journal(User.current, params[:notes])
100 params[:attachments].each { |file|
118 params[:attachments].each { |file|
101 next unless file.size > 0
119 next unless file.size > 0
102 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
120 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
103 journal.details << JournalDetail.new(:property => 'attachment',
121 journal.details << JournalDetail.new(:property => 'attachment',
104 :prop_key => a.id,
122 :prop_key => a.id,
105 :value => a.filename) unless a.new_record?
123 :value => a.filename) unless a.new_record?
106 } if params[:attachments] and params[:attachments].is_a? Array
124 } if params[:attachments] and params[:attachments].is_a? Array
107 if journal.save
125 if journal.save
108 flash[:notice] = l(:notice_successful_update)
126 flash[:notice] = l(:notice_successful_update)
109 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
127 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
110 redirect_to :action => 'show', :id => @issue
128 redirect_to :action => 'show', :id => @issue
111 return
129 return
112 end
130 end
113 show
131 show
114 end
132 end
115
133
116 def change_status
134 def change_status
117 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
135 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
118 @new_status = IssueStatus.find(params[:new_status_id])
136 @new_status = IssueStatus.find(params[:new_status_id])
119 if params[:confirm]
137 if params[:confirm]
120 begin
138 begin
121 journal = @issue.init_journal(self.logged_in_user, params[:notes])
139 journal = @issue.init_journal(self.logged_in_user, params[:notes])
122 @issue.status = @new_status
140 @issue.status = @new_status
123 if @issue.update_attributes(params[:issue])
141 if @issue.update_attributes(params[:issue])
124 # Save attachments
142 # Save attachments
125 params[:attachments].each { |file|
143 params[:attachments].each { |file|
126 next unless file.size > 0
144 next unless file.size > 0
127 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
145 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
128 journal.details << JournalDetail.new(:property => 'attachment',
146 journal.details << JournalDetail.new(:property => 'attachment',
129 :prop_key => a.id,
147 :prop_key => a.id,
130 :value => a.filename) unless a.new_record?
148 :value => a.filename) unless a.new_record?
131 } if params[:attachments] and params[:attachments].is_a? Array
149 } if params[:attachments] and params[:attachments].is_a? Array
132
150
133 # Log time
151 # Log time
134 if current_role.allowed_to?(:log_time)
152 if current_role.allowed_to?(:log_time)
135 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
153 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
136 @time_entry.attributes = params[:time_entry]
154 @time_entry.attributes = params[:time_entry]
137 @time_entry.save
155 @time_entry.save
138 end
156 end
139
157
140 flash[:notice] = l(:notice_successful_update)
158 flash[:notice] = l(:notice_successful_update)
141 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
159 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
142 redirect_to :action => 'show', :id => @issue
160 redirect_to :action => 'show', :id => @issue
143 end
161 end
144 rescue ActiveRecord::StaleObjectError
162 rescue ActiveRecord::StaleObjectError
145 # Optimistic locking exception
163 # Optimistic locking exception
146 flash[:error] = l(:notice_locking_conflict)
164 flash[:error] = l(:notice_locking_conflict)
147 end
165 end
148 end
166 end
149 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
167 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
150 @activities = Enumeration::get_values('ACTI')
168 @activities = Enumeration::get_values('ACTI')
151 end
169 end
152
170
153 def destroy
171 def destroy
154 @issue.destroy
172 @issue.destroy
155 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
173 redirect_to :action => 'index', :project_id => @project
156 end
174 end
157
175
158 def destroy_attachment
176 def destroy_attachment
159 a = @issue.attachments.find(params[:attachment_id])
177 a = @issue.attachments.find(params[:attachment_id])
160 a.destroy
178 a.destroy
161 journal = @issue.init_journal(self.logged_in_user)
179 journal = @issue.init_journal(self.logged_in_user)
162 journal.details << JournalDetail.new(:property => 'attachment',
180 journal.details << JournalDetail.new(:property => 'attachment',
163 :prop_key => a.id,
181 :prop_key => a.id,
164 :old_value => a.filename)
182 :old_value => a.filename)
165 journal.save
183 journal.save
166 redirect_to :action => 'show', :id => @issue
184 redirect_to :action => 'show', :id => @issue
167 end
185 end
168
186
169 def context_menu
187 def context_menu
170 @priorities = Enumeration.get_values('IPRI').reverse
188 @priorities = Enumeration.get_values('IPRI').reverse
171 @statuses = IssueStatus.find(:all, :order => 'position')
189 @statuses = IssueStatus.find(:all, :order => 'position')
172 @allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
190 @allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
173 @assignables = @issue.assignable_users
191 @assignables = @issue.assignable_users
174 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
192 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
175 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
193 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
176 :change_status => User.current.allowed_to?(:change_issue_status, @project),
194 :change_status => User.current.allowed_to?(:change_issue_status, @project),
177 :add => User.current.allowed_to?(:add_issues, @project),
195 :add => User.current.allowed_to?(:add_issues, @project),
178 :move => User.current.allowed_to?(:move_issues, @project),
196 :move => User.current.allowed_to?(:move_issues, @project),
179 :delete => User.current.allowed_to?(:delete_issues, @project)}
197 :delete => User.current.allowed_to?(:delete_issues, @project)}
180 render :layout => false
198 render :layout => false
181 end
199 end
182
200
183 def preview
201 def preview
184 issue = Issue.find_by_id(params[:id])
202 issue = Issue.find_by_id(params[:id])
185 @attachements = issue.attachments if issue
203 @attachements = issue.attachments if issue
186 @text = params[:issue][:description]
204 @text = params[:issue][:description]
187 render :partial => 'common/preview'
205 render :partial => 'common/preview'
188 end
206 end
189
207
190 private
208 private
191 def find_project
209 def find_project
192 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
210 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
193 @project = @issue.project
211 @project = @issue.project
194 rescue ActiveRecord::RecordNotFound
212 rescue ActiveRecord::RecordNotFound
195 render_404
213 render_404
196 end
214 end
197
215
216 def find_optional_project
217 return true unless params[:project_id]
218 @project = Project.find(params[:project_id])
219 authorize
220 rescue ActiveRecord::RecordNotFound
221 render_404
222 end
223
198 # Retrieve query from session or build a new query
224 # Retrieve query from session or build a new query
199 def retrieve_query
225 def retrieve_query
200 if params[:set_filter] or !session[:query] or session[:query].project_id
226 if params[:query_id]
227 @query = Query.find(params[:query_id], :conditions => {:project_id => (@project ? @project.id : nil)})
228 @query.executed_by = logged_in_user
229 session[:query] = @query
230 else
231 if params[:set_filter] or !session[:query] or session[:query].project != @project
201 # Give it a name, required to be valid
232 # Give it a name, required to be valid
202 @query = Query.new(:name => "_", :executed_by => logged_in_user)
233 @query = Query.new(:name => "_", :executed_by => logged_in_user)
234 @query.project = @project
203 if params[:fields] and params[:fields].is_a? Array
235 if params[:fields] and params[:fields].is_a? Array
204 params[:fields].each do |field|
236 params[:fields].each do |field|
205 @query.add_filter(field, params[:operators][field], params[:values][field])
237 @query.add_filter(field, params[:operators][field], params[:values][field])
206 end
238 end
207 else
239 else
208 @query.available_filters.keys.each do |field|
240 @query.available_filters.keys.each do |field|
209 @query.add_short_filter(field, params[field]) if params[field]
241 @query.add_short_filter(field, params[field]) if params[field]
210 end
242 end
211 end
243 end
212 session[:query] = @query
244 session[:query] = @query
213 else
245 else
214 @query = session[:query]
246 @query = session[:query]
215 end
247 end
216 end
248 end
217 end
249 end
250 end
@@ -1,689 +1,555
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'csv'
19
20 class ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
21 layout 'base'
19 layout 'base'
22 before_filter :find_project, :except => [ :index, :list, :add ]
20 before_filter :find_project, :except => [ :index, :list, :add ]
23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
21 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
22 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
25 accept_key_auth :activity, :calendar
23 accept_key_auth :activity, :calendar
26
24
27 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
25 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
28 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
26 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
29 cache_sweeper :version_sweeper, :only => [ :add_version ]
27 cache_sweeper :version_sweeper, :only => [ :add_version ]
30
28
31 helper :sort
29 helper :sort
32 include SortHelper
30 include SortHelper
33 helper :custom_fields
31 helper :custom_fields
34 include CustomFieldsHelper
32 include CustomFieldsHelper
35 helper :ifpdf
33 helper :ifpdf
36 include IfpdfHelper
34 include IfpdfHelper
37 helper :issues
35 helper :issues
38 helper IssuesHelper
36 helper IssuesHelper
39 helper :queries
37 helper :queries
40 include QueriesHelper
38 include QueriesHelper
41 helper :repositories
39 helper :repositories
42 include RepositoriesHelper
40 include RepositoriesHelper
43 include ProjectsHelper
41 include ProjectsHelper
44
42
45 def index
43 def index
46 list
44 list
47 render :action => 'list' unless request.xhr?
45 render :action => 'list' unless request.xhr?
48 end
46 end
49
47
50 # Lists visible projects
48 # Lists visible projects
51 def list
49 def list
52 projects = Project.find :all,
50 projects = Project.find :all,
53 :conditions => Project.visible_by(logged_in_user),
51 :conditions => Project.visible_by(logged_in_user),
54 :include => :parent
52 :include => :parent
55 @project_tree = projects.group_by {|p| p.parent || p}
53 @project_tree = projects.group_by {|p| p.parent || p}
56 @project_tree.each_key {|p| @project_tree[p] -= [p]}
54 @project_tree.each_key {|p| @project_tree[p] -= [p]}
57 end
55 end
58
56
59 # Add a new project
57 # Add a new project
60 def add
58 def add
61 @custom_fields = IssueCustomField.find(:all)
59 @custom_fields = IssueCustomField.find(:all)
62 @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
60 @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
63 @project = Project.new(params[:project])
61 @project = Project.new(params[:project])
64 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
62 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
65 if request.get?
63 if request.get?
66 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
64 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
67 else
65 else
68 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
66 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
69 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
67 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
70 @project.custom_values = @custom_values
68 @project.custom_values = @custom_values
71 if @project.save
69 if @project.save
72 @project.enabled_module_names = params[:enabled_modules]
70 @project.enabled_module_names = params[:enabled_modules]
73 flash[:notice] = l(:notice_successful_create)
71 flash[:notice] = l(:notice_successful_create)
74 redirect_to :controller => 'admin', :action => 'projects'
72 redirect_to :controller => 'admin', :action => 'projects'
75 end
73 end
76 end
74 end
77 end
75 end
78
76
79 # Show @project
77 # Show @project
80 def show
78 def show
81 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
79 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
82 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
80 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
83 @subprojects = @project.active_children
81 @subprojects = @project.active_children
84 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
82 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
85 @trackers = Tracker.find(:all, :order => 'position')
83 @trackers = Tracker.find(:all, :order => 'position')
86 @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])
84 @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])
87 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
85 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
88 @total_hours = @project.time_entries.sum(:hours)
86 @total_hours = @project.time_entries.sum(:hours)
89 @key = User.current.rss_key
87 @key = User.current.rss_key
90 end
88 end
91
89
92 def settings
90 def settings
93 @root_projects = Project::find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id])
91 @root_projects = Project::find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id])
94 @custom_fields = IssueCustomField.find(:all)
92 @custom_fields = IssueCustomField.find(:all)
95 @issue_category ||= IssueCategory.new
93 @issue_category ||= IssueCategory.new
96 @member ||= @project.members.new
94 @member ||= @project.members.new
97 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
95 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
98 @repository ||= @project.repository
96 @repository ||= @project.repository
99 @wiki ||= @project.wiki
97 @wiki ||= @project.wiki
100 end
98 end
101
99
102 # Edit @project
100 # Edit @project
103 def edit
101 def edit
104 if request.post?
102 if request.post?
105 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
103 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
106 if params[:custom_fields]
104 if params[:custom_fields]
107 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
105 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
108 @project.custom_values = @custom_values
106 @project.custom_values = @custom_values
109 end
107 end
110 @project.attributes = params[:project]
108 @project.attributes = params[:project]
111 if @project.save
109 if @project.save
112 flash[:notice] = l(:notice_successful_update)
110 flash[:notice] = l(:notice_successful_update)
113 redirect_to :action => 'settings', :id => @project
111 redirect_to :action => 'settings', :id => @project
114 else
112 else
115 settings
113 settings
116 render :action => 'settings'
114 render :action => 'settings'
117 end
115 end
118 end
116 end
119 end
117 end
120
118
121 def modules
119 def modules
122 @project.enabled_module_names = params[:enabled_modules]
120 @project.enabled_module_names = params[:enabled_modules]
123 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
121 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
124 end
122 end
125
123
126 def archive
124 def archive
127 @project.archive if request.post? && @project.active?
125 @project.archive if request.post? && @project.active?
128 redirect_to :controller => 'admin', :action => 'projects'
126 redirect_to :controller => 'admin', :action => 'projects'
129 end
127 end
130
128
131 def unarchive
129 def unarchive
132 @project.unarchive if request.post? && !@project.active?
130 @project.unarchive if request.post? && !@project.active?
133 redirect_to :controller => 'admin', :action => 'projects'
131 redirect_to :controller => 'admin', :action => 'projects'
134 end
132 end
135
133
136 # Delete @project
134 # Delete @project
137 def destroy
135 def destroy
138 @project_to_destroy = @project
136 @project_to_destroy = @project
139 if request.post? and params[:confirm]
137 if request.post? and params[:confirm]
140 @project_to_destroy.destroy
138 @project_to_destroy.destroy
141 redirect_to :controller => 'admin', :action => 'projects'
139 redirect_to :controller => 'admin', :action => 'projects'
142 end
140 end
143 # hide project in layout
141 # hide project in layout
144 @project = nil
142 @project = nil
145 end
143 end
146
144
147 # Add a new issue category to @project
145 # Add a new issue category to @project
148 def add_issue_category
146 def add_issue_category
149 @category = @project.issue_categories.build(params[:category])
147 @category = @project.issue_categories.build(params[:category])
150 if request.post? and @category.save
148 if request.post? and @category.save
151 respond_to do |format|
149 respond_to do |format|
152 format.html do
150 format.html do
153 flash[:notice] = l(:notice_successful_create)
151 flash[:notice] = l(:notice_successful_create)
154 redirect_to :action => 'settings', :tab => 'categories', :id => @project
152 redirect_to :action => 'settings', :tab => 'categories', :id => @project
155 end
153 end
156 format.js do
154 format.js do
157 # IE doesn't support the replace_html rjs method for select box options
155 # IE doesn't support the replace_html rjs method for select box options
158 render(:update) {|page| page.replace "issue_category_id",
156 render(:update) {|page| page.replace "issue_category_id",
159 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]')
157 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]')
160 }
158 }
161 end
159 end
162 end
160 end
163 end
161 end
164 end
162 end
165
163
166 # Add a new version to @project
164 # Add a new version to @project
167 def add_version
165 def add_version
168 @version = @project.versions.build(params[:version])
166 @version = @project.versions.build(params[:version])
169 if request.post? and @version.save
167 if request.post? and @version.save
170 flash[:notice] = l(:notice_successful_create)
168 flash[:notice] = l(:notice_successful_create)
171 redirect_to :action => 'settings', :tab => 'versions', :id => @project
169 redirect_to :action => 'settings', :tab => 'versions', :id => @project
172 end
170 end
173 end
171 end
174
172
175 # Add a new document to @project
173 # Add a new document to @project
176 def add_document
174 def add_document
177 @document = @project.documents.build(params[:document])
175 @document = @project.documents.build(params[:document])
178 if request.post? and @document.save
176 if request.post? and @document.save
179 # Save the attachments
177 # Save the attachments
180 params[:attachments].each { |a|
178 params[:attachments].each { |a|
181 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
179 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
182 } if params[:attachments] and params[:attachments].is_a? Array
180 } if params[:attachments] and params[:attachments].is_a? Array
183 flash[:notice] = l(:notice_successful_create)
181 flash[:notice] = l(:notice_successful_create)
184 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
182 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
185 redirect_to :action => 'list_documents', :id => @project
183 redirect_to :action => 'list_documents', :id => @project
186 end
184 end
187 end
185 end
188
186
189 # Show documents list of @project
187 # Show documents list of @project
190 def list_documents
188 def list_documents
191 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
189 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
192 documents = @project.documents.find :all, :include => [:attachments, :category]
190 documents = @project.documents.find :all, :include => [:attachments, :category]
193 case @sort_by
191 case @sort_by
194 when 'date'
192 when 'date'
195 @grouped = documents.group_by {|d| d.created_on.to_date }
193 @grouped = documents.group_by {|d| d.created_on.to_date }
196 when 'title'
194 when 'title'
197 @grouped = documents.group_by {|d| d.title.first.upcase}
195 @grouped = documents.group_by {|d| d.title.first.upcase}
198 when 'author'
196 when 'author'
199 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
197 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
200 else
198 else
201 @grouped = documents.group_by(&:category)
199 @grouped = documents.group_by(&:category)
202 end
200 end
203 render :layout => false if request.xhr?
201 render :layout => false if request.xhr?
204 end
202 end
205
203
206 # Add a new issue to @project
204 # Add a new issue to @project
207 # The new issue will be created from an existing one if copy_from parameter is given
205 # The new issue will be created from an existing one if copy_from parameter is given
208 def add_issue
206 def add_issue
209 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
207 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
210 @issue.project = @project
208 @issue.project = @project
211 @issue.author = User.current
209 @issue.author = User.current
212 @issue.tracker ||= Tracker.find(params[:tracker_id])
210 @issue.tracker ||= Tracker.find(params[:tracker_id])
213
211
214 default_status = IssueStatus.default
212 default_status = IssueStatus.default
215 unless default_status
213 unless default_status
216 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
214 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
217 render :nothing => true, :layout => true
215 render :nothing => true, :layout => true
218 return
216 return
219 end
217 end
220 @issue.status = default_status
218 @issue.status = default_status
221 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
219 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
222
220
223 if request.get?
221 if request.get?
224 @issue.start_date ||= Date.today
222 @issue.start_date ||= Date.today
225 @custom_values = @issue.custom_values.empty? ?
223 @custom_values = @issue.custom_values.empty? ?
226 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
224 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
227 @issue.custom_values
225 @issue.custom_values
228 else
226 else
229 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
227 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
230 # Check that the user is allowed to apply the requested status
228 # Check that the user is allowed to apply the requested status
231 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
229 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
232 @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]) }
230 @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]) }
233 @issue.custom_values = @custom_values
231 @issue.custom_values = @custom_values
234 if @issue.save
232 if @issue.save
235 if params[:attachments] && params[:attachments].is_a?(Array)
233 if params[:attachments] && params[:attachments].is_a?(Array)
236 # Save attachments
234 # Save attachments
237 params[:attachments].each {|a| Attachment.create(:container => @issue, :file => a, :author => User.current) unless a.size == 0}
235 params[:attachments].each {|a| Attachment.create(:container => @issue, :file => a, :author => User.current) unless a.size == 0}
238 end
236 end
239 flash[:notice] = l(:notice_successful_create)
237 flash[:notice] = l(:notice_successful_create)
240 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
238 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
241 redirect_to :action => 'list_issues', :id => @project
239 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
242 return
240 return
243 end
241 end
244 end
242 end
245 @priorities = Enumeration::get_values('IPRI')
243 @priorities = Enumeration::get_values('IPRI')
246 end
244 end
247
245
248 # Show filtered/sorted issues list of @project
249 def list_issues
250 sort_init "#{Issue.table_name}.id", "desc"
251 sort_update
252
253 retrieve_query
254
255 @results_per_page_options = [ 15, 25, 50, 100 ]
256 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
257 @results_per_page = params[:per_page].to_i
258 session[:results_per_page] = @results_per_page
259 else
260 @results_per_page = session[:results_per_page] || 25
261 end
262
263 if @query.valid?
264 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
265 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
266 @issues = Issue.find :all, :order => sort_clause,
267 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
268 :conditions => @query.statement,
269 :limit => @issue_pages.items_per_page,
270 :offset => @issue_pages.current.offset
271 end
272
273 render :layout => false if request.xhr?
274 end
275
276 # Export filtered/sorted issues list to CSV
277 def export_issues_csv
278 sort_init "#{Issue.table_name}.id", "desc"
279 sort_update
280
281 retrieve_query
282 render :action => 'list_issues' and return unless @query.valid?
283
284 @issues = Issue.find :all, :order => sort_clause,
285 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
286 :conditions => @query.statement,
287 :limit => Setting.issues_export_limit.to_i
288
289 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
290 export = StringIO.new
291 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
292 # csv header fields
293 headers = [ "#", l(:field_status),
294 l(:field_project),
295 l(:field_tracker),
296 l(:field_priority),
297 l(:field_subject),
298 l(:field_assigned_to),
299 l(:field_author),
300 l(:field_start_date),
301 l(:field_due_date),
302 l(:field_done_ratio),
303 l(:field_created_on),
304 l(:field_updated_on)
305 ]
306 for custom_field in @project.all_custom_fields
307 headers << custom_field.name
308 end
309 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
310 # csv lines
311 @issues.each do |issue|
312 fields = [issue.id, issue.status.name,
313 issue.project.name,
314 issue.tracker.name,
315 issue.priority.name,
316 issue.subject,
317 (issue.assigned_to ? issue.assigned_to.name : ""),
318 issue.author.name,
319 issue.start_date ? l_date(issue.start_date) : nil,
320 issue.due_date ? l_date(issue.due_date) : nil,
321 issue.done_ratio,
322 l_datetime(issue.created_on),
323 l_datetime(issue.updated_on)
324 ]
325 for custom_field in @project.all_custom_fields
326 fields << (show_value issue.custom_value_for(custom_field))
327 end
328 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
329 end
330 end
331 export.rewind
332 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
333 end
334
335 # Export filtered/sorted issues to PDF
336 def export_issues_pdf
337 sort_init "#{Issue.table_name}.id", "desc"
338 sort_update
339
340 retrieve_query
341 render :action => 'list_issues' and return unless @query.valid?
342
343 @issues = Issue.find :all, :order => sort_clause,
344 :include => [ :author, :status, :tracker, :priority, :project ],
345 :conditions => @query.statement,
346 :limit => Setting.issues_export_limit.to_i
347
348 @options_for_rfpdf ||= {}
349 @options_for_rfpdf[:file_name] = "export.pdf"
350 render :layout => false
351 end
352
353 # Bulk edit issues
246 # Bulk edit issues
354 def bulk_edit_issues
247 def bulk_edit_issues
355 if request.post?
248 if request.post?
356 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
249 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
357 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
250 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
358 assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
251 assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
359 category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
252 category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
360 fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
253 fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
361 issues = @project.issues.find_all_by_id(params[:issue_ids])
254 issues = @project.issues.find_all_by_id(params[:issue_ids])
362 unsaved_issue_ids = []
255 unsaved_issue_ids = []
363 issues.each do |issue|
256 issues.each do |issue|
364 journal = issue.init_journal(User.current, params[:notes])
257 journal = issue.init_journal(User.current, params[:notes])
365 issue.priority = priority if priority
258 issue.priority = priority if priority
366 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
259 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
367 issue.category = category if category
260 issue.category = category if category
368 issue.fixed_version = fixed_version if fixed_version
261 issue.fixed_version = fixed_version if fixed_version
369 issue.start_date = params[:start_date] unless params[:start_date].blank?
262 issue.start_date = params[:start_date] unless params[:start_date].blank?
370 issue.due_date = params[:due_date] unless params[:due_date].blank?
263 issue.due_date = params[:due_date] unless params[:due_date].blank?
371 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
264 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
372 # Don't save any change to the issue if the user is not authorized to apply the requested status
265 # Don't save any change to the issue if the user is not authorized to apply the requested status
373 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
266 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
374 # Send notification for each issue (if changed)
267 # Send notification for each issue (if changed)
375 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
268 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
376 else
269 else
377 # Keep unsaved issue ids to display them in flash error
270 # Keep unsaved issue ids to display them in flash error
378 unsaved_issue_ids << issue.id
271 unsaved_issue_ids << issue.id
379 end
272 end
380 end
273 end
381 if unsaved_issue_ids.empty?
274 if unsaved_issue_ids.empty?
382 flash[:notice] = l(:notice_successful_update) unless issues.empty?
275 flash[:notice] = l(:notice_successful_update) unless issues.empty?
383 else
276 else
384 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
277 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
385 end
278 end
386 redirect_to :action => 'list_issues', :id => @project
279 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
387 return
280 return
388 end
281 end
389 if current_role && User.current.allowed_to?(:change_issue_status, @project)
282 if current_role && User.current.allowed_to?(:change_issue_status, @project)
390 # Find potential statuses the user could be allowed to switch issues to
283 # Find potential statuses the user could be allowed to switch issues to
391 @available_statuses = Workflow.find(:all, :include => :new_status,
284 @available_statuses = Workflow.find(:all, :include => :new_status,
392 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
285 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
393 end
286 end
394 render :update do |page|
287 render :update do |page|
395 page.hide 'query_form'
288 page.hide 'query_form'
396 page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
289 page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
397 end
290 end
398 end
291 end
399
292
400 def move_issues
293 def move_issues
401 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
294 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
402 redirect_to :action => 'list_issues', :id => @project and return unless @issues
295 redirect_to :controller => 'issues', :action => 'index', :project_id => @project and return unless @issues
403 @projects = []
296 @projects = []
404 # find projects to which the user is allowed to move the issue
297 # find projects to which the user is allowed to move the issue
405 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:controller => 'projects', :action => 'move_issues')}
298 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:controller => 'projects', :action => 'move_issues')}
406 # issue can be moved to any tracker
299 # issue can be moved to any tracker
407 @trackers = Tracker.find(:all)
300 @trackers = Tracker.find(:all)
408 if request.post? and params[:new_project_id] and params[:new_tracker_id]
301 if request.post? and params[:new_project_id] and params[:new_tracker_id]
409 new_project = Project.find_by_id(params[:new_project_id])
302 new_project = Project.find_by_id(params[:new_project_id])
410 new_tracker = Tracker.find_by_id(params[:new_tracker_id])
303 new_tracker = Tracker.find_by_id(params[:new_tracker_id])
411 @issues.each do |i|
304 @issues.each do |i|
412 if new_project && i.project_id != new_project.id
305 if new_project && i.project_id != new_project.id
413 # issue is moved to another project
306 # issue is moved to another project
414 i.category = nil
307 i.category = nil
415 i.fixed_version = nil
308 i.fixed_version = nil
416 # delete issue relations
309 # delete issue relations
417 i.relations_from.clear
310 i.relations_from.clear
418 i.relations_to.clear
311 i.relations_to.clear
419 i.project = new_project
312 i.project = new_project
420 end
313 end
421 if new_tracker
314 if new_tracker
422 i.tracker = new_tracker
315 i.tracker = new_tracker
423 end
316 end
424 i.save
317 i.save
425 end
318 end
426 flash[:notice] = l(:notice_successful_update)
319 flash[:notice] = l(:notice_successful_update)
427 redirect_to :action => 'list_issues', :id => @project
320 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
428 end
321 end
429 end
322 end
430
323
431 # Add a news to @project
324 # Add a news to @project
432 def add_news
325 def add_news
433 @news = News.new(:project => @project)
326 @news = News.new(:project => @project)
434 if request.post?
327 if request.post?
435 @news.attributes = params[:news]
328 @news.attributes = params[:news]
436 @news.author_id = self.logged_in_user.id if self.logged_in_user
329 @news.author_id = self.logged_in_user.id if self.logged_in_user
437 if @news.save
330 if @news.save
438 flash[:notice] = l(:notice_successful_create)
331 flash[:notice] = l(:notice_successful_create)
439 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
332 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
440 redirect_to :action => 'list_news', :id => @project
333 redirect_to :action => 'list_news', :id => @project
441 end
334 end
442 end
335 end
443 end
336 end
444
337
445 # Show news list of @project
338 # Show news list of @project
446 def list_news
339 def list_news
447 @news_pages, @newss = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
340 @news_pages, @newss = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
448
341
449 respond_to do |format|
342 respond_to do |format|
450 format.html { render :layout => false if request.xhr? }
343 format.html { render :layout => false if request.xhr? }
451 format.atom { render_feed(@newss, :title => "#{@project.name}: #{l(:label_news_plural)}") }
344 format.atom { render_feed(@newss, :title => "#{@project.name}: #{l(:label_news_plural)}") }
452 end
345 end
453 end
346 end
454
347
455 def add_file
348 def add_file
456 if request.post?
349 if request.post?
457 @version = @project.versions.find_by_id(params[:version_id])
350 @version = @project.versions.find_by_id(params[:version_id])
458 # Save the attachments
351 # Save the attachments
459 @attachments = []
352 @attachments = []
460 params[:attachments].each { |file|
353 params[:attachments].each { |file|
461 next unless file.size > 0
354 next unless file.size > 0
462 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
355 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
463 @attachments << a unless a.new_record?
356 @attachments << a unless a.new_record?
464 } if params[:attachments] and params[:attachments].is_a? Array
357 } if params[:attachments] and params[:attachments].is_a? Array
465 Mailer.deliver_attachments_added(@attachments) if !@attachments.empty? && Setting.notified_events.include?('file_added')
358 Mailer.deliver_attachments_added(@attachments) if !@attachments.empty? && Setting.notified_events.include?('file_added')
466 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
359 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
467 end
360 end
468 @versions = @project.versions.sort
361 @versions = @project.versions.sort
469 end
362 end
470
363
471 def list_files
364 def list_files
472 @versions = @project.versions.sort
365 @versions = @project.versions.sort
473 end
366 end
474
367
475 # Show changelog for @project
368 # Show changelog for @project
476 def changelog
369 def changelog
477 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
370 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
478 retrieve_selected_tracker_ids(@trackers)
371 retrieve_selected_tracker_ids(@trackers)
479 @versions = @project.versions.sort
372 @versions = @project.versions.sort
480 end
373 end
481
374
482 def roadmap
375 def roadmap
483 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
376 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
484 retrieve_selected_tracker_ids(@trackers)
377 retrieve_selected_tracker_ids(@trackers)
485 @versions = @project.versions.sort
378 @versions = @project.versions.sort
486 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
379 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
487 end
380 end
488
381
489 def activity
382 def activity
490 if params[:year] and params[:year].to_i > 1900
383 if params[:year] and params[:year].to_i > 1900
491 @year = params[:year].to_i
384 @year = params[:year].to_i
492 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
385 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
493 @month = params[:month].to_i
386 @month = params[:month].to_i
494 end
387 end
495 end
388 end
496 @year ||= Date.today.year
389 @year ||= Date.today.year
497 @month ||= Date.today.month
390 @month ||= Date.today.month
498
391
499 case params[:format]
392 case params[:format]
500 when 'atom'
393 when 'atom'
501 # 30 last days
394 # 30 last days
502 @date_from = Date.today - 30
395 @date_from = Date.today - 30
503 @date_to = Date.today + 1
396 @date_to = Date.today + 1
504 else
397 else
505 # current month
398 # current month
506 @date_from = Date.civil(@year, @month, 1)
399 @date_from = Date.civil(@year, @month, 1)
507 @date_to = @date_from >> 1
400 @date_to = @date_from >> 1
508 end
401 end
509
402
510 @event_types = %w(issues news files documents wiki_pages changesets)
403 @event_types = %w(issues news files documents wiki_pages changesets)
511 @event_types.delete('wiki_pages') unless @project.wiki
404 @event_types.delete('wiki_pages') unless @project.wiki
512 @event_types.delete('changesets') unless @project.repository
405 @event_types.delete('changesets') unless @project.repository
513 # only show what the user is allowed to view
406 # only show what the user is allowed to view
514 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
407 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
515
408
516 @scope = @event_types.select {|t| params["show_#{t}"]}
409 @scope = @event_types.select {|t| params["show_#{t}"]}
517 # default events if none is specified in parameters
410 # default events if none is specified in parameters
518 @scope = (@event_types - %w(wiki_pages))if @scope.empty?
411 @scope = (@event_types - %w(wiki_pages))if @scope.empty?
519
412
520 @events = []
413 @events = []
521
414
522 if @scope.include?('issues')
415 if @scope.include?('issues')
523 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
416 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
524 end
417 end
525
418
526 if @scope.include?('news')
419 if @scope.include?('news')
527 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
420 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
528 end
421 end
529
422
530 if @scope.include?('files')
423 if @scope.include?('files')
531 @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 )
424 @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 )
532 end
425 end
533
426
534 if @scope.include?('documents')
427 if @scope.include?('documents')
535 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
428 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
536 @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 )
429 @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 )
537 end
430 end
538
431
539 if @scope.include?('wiki_pages')
432 if @scope.include?('wiki_pages')
540 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
433 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
541 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
434 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
542 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
435 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
543 "#{WikiContent.versioned_table_name}.id"
436 "#{WikiContent.versioned_table_name}.id"
544 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
437 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
545 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
438 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
546 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
439 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
547 @project.id, @date_from, @date_to]
440 @project.id, @date_from, @date_to]
548
441
549 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
442 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
550 end
443 end
551
444
552 if @scope.include?('changesets')
445 if @scope.include?('changesets')
553 @events += @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
446 @events += @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
554 end
447 end
555
448
556 @events_by_day = @events.group_by(&:event_date)
449 @events_by_day = @events.group_by(&:event_date)
557
450
558 respond_to do |format|
451 respond_to do |format|
559 format.html { render :layout => false if request.xhr? }
452 format.html { render :layout => false if request.xhr? }
560 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
453 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
561 end
454 end
562 end
455 end
563
456
564 def calendar
457 def calendar
565 @trackers = Tracker.find(:all, :order => 'position')
458 @trackers = Tracker.find(:all, :order => 'position')
566 retrieve_selected_tracker_ids(@trackers)
459 retrieve_selected_tracker_ids(@trackers)
567
460
568 if params[:year] and params[:year].to_i > 1900
461 if params[:year] and params[:year].to_i > 1900
569 @year = params[:year].to_i
462 @year = params[:year].to_i
570 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
463 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
571 @month = params[:month].to_i
464 @month = params[:month].to_i
572 end
465 end
573 end
466 end
574 @year ||= Date.today.year
467 @year ||= Date.today.year
575 @month ||= Date.today.month
468 @month ||= Date.today.month
576 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
469 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
577
470
578 events = []
471 events = []
579 @project.issues_with_subprojects(params[:with_subprojects]) do
472 @project.issues_with_subprojects(params[:with_subprojects]) do
580 events += Issue.find(:all,
473 events += Issue.find(:all,
581 :include => [:tracker, :status, :assigned_to, :priority, :project],
474 :include => [:tracker, :status, :assigned_to, :priority, :project],
582 :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]
475 :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]
583 ) unless @selected_tracker_ids.empty?
476 ) unless @selected_tracker_ids.empty?
584 end
477 end
585 events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
478 events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
586 @calendar.events = events
479 @calendar.events = events
587
480
588 render :layout => false if request.xhr?
481 render :layout => false if request.xhr?
589 end
482 end
590
483
591 def gantt
484 def gantt
592 @trackers = Tracker.find(:all, :order => 'position')
485 @trackers = Tracker.find(:all, :order => 'position')
593 retrieve_selected_tracker_ids(@trackers)
486 retrieve_selected_tracker_ids(@trackers)
594
487
595 if params[:year] and params[:year].to_i >0
488 if params[:year] and params[:year].to_i >0
596 @year_from = params[:year].to_i
489 @year_from = params[:year].to_i
597 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
490 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
598 @month_from = params[:month].to_i
491 @month_from = params[:month].to_i
599 else
492 else
600 @month_from = 1
493 @month_from = 1
601 end
494 end
602 else
495 else
603 @month_from ||= Date.today.month
496 @month_from ||= Date.today.month
604 @year_from ||= Date.today.year
497 @year_from ||= Date.today.year
605 end
498 end
606
499
607 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
500 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
608 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
501 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
609 months = (params[:months] || User.current.pref[:gantt_months]).to_i
502 months = (params[:months] || User.current.pref[:gantt_months]).to_i
610 @months = (months > 0 && months < 25) ? months : 6
503 @months = (months > 0 && months < 25) ? months : 6
611
504
612 # Save gantt paramters as user preference (zoom and months count)
505 # Save gantt paramters as user preference (zoom and months count)
613 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
506 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
614 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
507 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
615 User.current.preference.save
508 User.current.preference.save
616 end
509 end
617
510
618 @date_from = Date.civil(@year_from, @month_from, 1)
511 @date_from = Date.civil(@year_from, @month_from, 1)
619 @date_to = (@date_from >> @months) - 1
512 @date_to = (@date_from >> @months) - 1
620
513
621 @events = []
514 @events = []
622 @project.issues_with_subprojects(params[:with_subprojects]) do
515 @project.issues_with_subprojects(params[:with_subprojects]) do
623 @events += Issue.find(:all,
516 @events += Issue.find(:all,
624 :order => "start_date, due_date",
517 :order => "start_date, due_date",
625 :include => [:tracker, :status, :assigned_to, :priority, :project],
518 :include => [:tracker, :status, :assigned_to, :priority, :project],
626 :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]
519 :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]
627 ) unless @selected_tracker_ids.empty?
520 ) unless @selected_tracker_ids.empty?
628 end
521 end
629 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
522 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
630 @events.sort! {|x,y| x.start_date <=> y.start_date }
523 @events.sort! {|x,y| x.start_date <=> y.start_date }
631
524
632 if params[:format]=='pdf'
525 if params[:format]=='pdf'
633 @options_for_rfpdf ||= {}
526 @options_for_rfpdf ||= {}
634 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
527 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
635 render :template => "projects/gantt.rfpdf", :layout => false
528 render :template => "projects/gantt.rfpdf", :layout => false
636 elsif params[:format]=='png' && respond_to?('gantt_image')
529 elsif params[:format]=='png' && respond_to?('gantt_image')
637 image = gantt_image(@events, @date_from, @months, @zoom)
530 image = gantt_image(@events, @date_from, @months, @zoom)
638 image.format = 'PNG'
531 image.format = 'PNG'
639 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
532 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
640 else
533 else
641 render :template => "projects/gantt.rhtml"
534 render :template => "projects/gantt.rhtml"
642 end
535 end
643 end
536 end
644
537
645 private
538 private
646 # Find project of id params[:id]
539 # Find project of id params[:id]
647 # if not found, redirect to project list
540 # if not found, redirect to project list
648 # Used as a before_filter
541 # Used as a before_filter
649 def find_project
542 def find_project
650 @project = Project.find(params[:id])
543 @project = Project.find(params[:id])
651 rescue ActiveRecord::RecordNotFound
544 rescue ActiveRecord::RecordNotFound
652 render_404
545 render_404
653 end
546 end
654
547
655 def retrieve_selected_tracker_ids(selectable_trackers)
548 def retrieve_selected_tracker_ids(selectable_trackers)
656 if ids = params[:tracker_ids]
549 if ids = params[:tracker_ids]
657 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
550 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
658 else
551 else
659 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
552 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
660 end
553 end
661 end
554 end
662
663 # Retrieve query from session or build a new query
664 def retrieve_query
665 if params[:query_id]
666 @query = @project.queries.find(params[:query_id])
667 @query.executed_by = logged_in_user
668 session[:query] = @query
669 else
670 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
671 # Give it a name, required to be valid
672 @query = Query.new(:name => "_", :executed_by => logged_in_user)
673 @query.project = @project
674 if params[:fields] and params[:fields].is_a? Array
675 params[:fields].each do |field|
676 @query.add_filter(field, params[:operators][field], params[:values][field])
677 end
678 else
679 @query.available_filters.keys.each do |field|
680 @query.add_short_filter(field, params[field]) if params[field]
681 end
682 end
683 session[:query] = @query
684 else
685 @query = session[:query]
686 end
687 end
688 end
689 end
555 end
@@ -1,83 +1,83
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueriesController < ApplicationController
18 class QueriesController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
21
21
22 def index
22 def index
23 @queries = @project.queries.find(:all,
23 @queries = @project.queries.find(:all,
24 :order => "name ASC",
24 :order => "name ASC",
25 :conditions => ["is_public = ? or user_id = ?", true, (logged_in_user ? logged_in_user.id : 0)])
25 :conditions => ["is_public = ? or user_id = ?", true, (logged_in_user ? logged_in_user.id : 0)])
26 end
26 end
27
27
28 def new
28 def new
29 @query = Query.new(params[:query])
29 @query = Query.new(params[:query])
30 @query.project = @project
30 @query.project = @project
31 @query.user = logged_in_user
31 @query.user = logged_in_user
32 @query.executed_by = logged_in_user
32 @query.executed_by = logged_in_user
33 @query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
33 @query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
34 @query.column_names = nil if params[:default_columns]
34 @query.column_names = nil if params[:default_columns]
35
35
36 params[:fields].each do |field|
36 params[:fields].each do |field|
37 @query.add_filter(field, params[:operators][field], params[:values][field])
37 @query.add_filter(field, params[:operators][field], params[:values][field])
38 end if params[:fields]
38 end if params[:fields]
39
39
40 if request.post? && params[:confirm] && @query.save
40 if request.post? && params[:confirm] && @query.save
41 flash[:notice] = l(:notice_successful_create)
41 flash[:notice] = l(:notice_successful_create)
42 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query
42 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
43 return
43 return
44 end
44 end
45 render :layout => false if request.xhr?
45 render :layout => false if request.xhr?
46 end
46 end
47
47
48 def edit
48 def edit
49 if request.post?
49 if request.post?
50 @query.filters = {}
50 @query.filters = {}
51 params[:fields].each do |field|
51 params[:fields].each do |field|
52 @query.add_filter(field, params[:operators][field], params[:values][field])
52 @query.add_filter(field, params[:operators][field], params[:values][field])
53 end if params[:fields]
53 end if params[:fields]
54 @query.attributes = params[:query]
54 @query.attributes = params[:query]
55 @query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
55 @query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
56 @query.column_names = nil if params[:default_columns]
56 @query.column_names = nil if params[:default_columns]
57
57
58 if @query.save
58 if @query.save
59 flash[:notice] = l(:notice_successful_update)
59 flash[:notice] = l(:notice_successful_update)
60 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query
60 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
61 end
61 end
62 end
62 end
63 end
63 end
64
64
65 def destroy
65 def destroy
66 @query.destroy if request.post?
66 @query.destroy if request.post?
67 redirect_to :controller => 'queries', :project_id => @project
67 redirect_to :controller => 'queries', :project_id => @project
68 end
68 end
69
69
70 private
70 private
71 def find_project
71 def find_project
72 if params[:id]
72 if params[:id]
73 @query = Query.find(params[:id])
73 @query = Query.find(params[:id])
74 @query.executed_by = logged_in_user
74 @query.executed_by = logged_in_user
75 @project = @query.project
75 @project = @query.project
76 render_403 unless @query.editable_by?(logged_in_user)
76 render_403 unless @query.editable_by?(logged_in_user)
77 else
77 else
78 @project = Project.find(params[:project_id])
78 @project = Project.find(params[:project_id])
79 end
79 end
80 rescue ActiveRecord::RecordNotFound
80 rescue ActiveRecord::RecordNotFound
81 render_404
81 render_404
82 end
82 end
83 end
83 end
@@ -1,104 +1,156
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'csv'
19
18 module IssuesHelper
20 module IssuesHelper
19
21
20 def render_issue_tooltip(issue)
22 def render_issue_tooltip(issue)
21 @cached_label_start_date ||= l(:field_start_date)
23 @cached_label_start_date ||= l(:field_start_date)
22 @cached_label_due_date ||= l(:field_due_date)
24 @cached_label_due_date ||= l(:field_due_date)
23 @cached_label_assigned_to ||= l(:field_assigned_to)
25 @cached_label_assigned_to ||= l(:field_assigned_to)
24 @cached_label_priority ||= l(:field_priority)
26 @cached_label_priority ||= l(:field_priority)
25
27
26 link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" +
28 link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" +
27 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
29 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
28 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
30 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
29 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
31 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
30 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
32 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
31 end
33 end
32
34
33 def show_detail(detail, no_html=false)
35 def show_detail(detail, no_html=false)
34 case detail.property
36 case detail.property
35 when 'attr'
37 when 'attr'
36 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
38 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
37 case detail.prop_key
39 case detail.prop_key
38 when 'due_date', 'start_date'
40 when 'due_date', 'start_date'
39 value = format_date(detail.value.to_date) if detail.value
41 value = format_date(detail.value.to_date) if detail.value
40 old_value = format_date(detail.old_value.to_date) if detail.old_value
42 old_value = format_date(detail.old_value.to_date) if detail.old_value
41 when 'status_id'
43 when 'status_id'
42 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
44 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
43 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
45 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
44 when 'assigned_to_id'
46 when 'assigned_to_id'
45 u = User.find_by_id(detail.value) and value = u.name if detail.value
47 u = User.find_by_id(detail.value) and value = u.name if detail.value
46 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
48 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
47 when 'priority_id'
49 when 'priority_id'
48 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
50 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
49 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
51 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
50 when 'category_id'
52 when 'category_id'
51 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
53 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
52 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
54 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
53 when 'fixed_version_id'
55 when 'fixed_version_id'
54 v = Version.find_by_id(detail.value) and value = v.name if detail.value
56 v = Version.find_by_id(detail.value) and value = v.name if detail.value
55 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
57 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
56 end
58 end
57 when 'cf'
59 when 'cf'
58 custom_field = CustomField.find_by_id(detail.prop_key)
60 custom_field = CustomField.find_by_id(detail.prop_key)
59 if custom_field
61 if custom_field
60 label = custom_field.name
62 label = custom_field.name
61 value = format_value(detail.value, custom_field.field_format) if detail.value
63 value = format_value(detail.value, custom_field.field_format) if detail.value
62 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
64 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
63 end
65 end
64 when 'attachment'
66 when 'attachment'
65 label = l(:label_attachment)
67 label = l(:label_attachment)
66 end
68 end
67
69
68 label ||= detail.prop_key
70 label ||= detail.prop_key
69 value ||= detail.value
71 value ||= detail.value
70 old_value ||= detail.old_value
72 old_value ||= detail.old_value
71
73
72 unless no_html
74 unless no_html
73 label = content_tag('strong', label)
75 label = content_tag('strong', label)
74 old_value = content_tag("i", h(old_value)) if detail.old_value
76 old_value = content_tag("i", h(old_value)) if detail.old_value
75 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
77 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
76 if detail.property == 'attachment' && !value.blank? && Attachment.find_by_id(detail.prop_key)
78 if detail.property == 'attachment' && !value.blank? && Attachment.find_by_id(detail.prop_key)
77 # Link to the attachment if it has not been removed
79 # Link to the attachment if it has not been removed
78 value = link_to(value, :controller => 'attachments', :action => 'download', :id => detail.prop_key)
80 value = link_to(value, :controller => 'attachments', :action => 'download', :id => detail.prop_key)
79 else
81 else
80 value = content_tag("i", h(value)) if value
82 value = content_tag("i", h(value)) if value
81 end
83 end
82 end
84 end
83
85
84 if !detail.value.blank?
86 if !detail.value.blank?
85 case detail.property
87 case detail.property
86 when 'attr', 'cf'
88 when 'attr', 'cf'
87 if !detail.old_value.blank?
89 if !detail.old_value.blank?
88 label + " " + l(:text_journal_changed, old_value, value)
90 label + " " + l(:text_journal_changed, old_value, value)
89 else
91 else
90 label + " " + l(:text_journal_set_to, value)
92 label + " " + l(:text_journal_set_to, value)
91 end
93 end
92 when 'attachment'
94 when 'attachment'
93 "#{label} #{value} #{l(:label_added)}"
95 "#{label} #{value} #{l(:label_added)}"
94 end
96 end
95 else
97 else
96 case detail.property
98 case detail.property
97 when 'attr', 'cf'
99 when 'attr', 'cf'
98 label + " " + l(:text_journal_deleted) + " (#{old_value})"
100 label + " " + l(:text_journal_deleted) + " (#{old_value})"
99 when 'attachment'
101 when 'attachment'
100 "#{label} #{old_value} #{l(:label_deleted)}"
102 "#{label} #{old_value} #{l(:label_deleted)}"
101 end
103 end
102 end
104 end
103 end
105 end
106
107 def issues_to_csv(issues, project = nil)
108 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
109 export = StringIO.new
110 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
111 # csv header fields
112 headers = [ "#",
113 l(:field_status),
114 l(:field_project),
115 l(:field_tracker),
116 l(:field_priority),
117 l(:field_subject),
118 l(:field_assigned_to),
119 l(:field_author),
120 l(:field_start_date),
121 l(:field_due_date),
122 l(:field_done_ratio),
123 l(:field_created_on),
124 l(:field_updated_on)
125 ]
126 # only export custom fields if project is given
127 for custom_field in project.all_custom_fields
128 headers << custom_field.name
129 end if project
130 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
131 # csv lines
132 issues.each do |issue|
133 fields = [issue.id,
134 issue.status.name,
135 issue.project.name,
136 issue.tracker.name,
137 issue.priority.name,
138 issue.subject,
139 (issue.assigned_to ? issue.assigned_to.name : ""),
140 issue.author.name,
141 issue.start_date ? l_date(issue.start_date) : nil,
142 issue.due_date ? l_date(issue.due_date) : nil,
143 issue.done_ratio,
144 l_datetime(issue.created_on),
145 l_datetime(issue.updated_on)
146 ]
147 for custom_field in project.all_custom_fields
148 fields << (show_value issue.custom_value_for(custom_field))
149 end if project
150 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
151 end
152 end
153 export.rewind
154 export
155 end
104 end
156 end
@@ -1,36 +1,40
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Journal < ActiveRecord::Base
18 class Journal < ActiveRecord::Base
19 belongs_to :journalized, :polymorphic => true
19 belongs_to :journalized, :polymorphic => true
20 # added as a quick fix to allow eager loading of the polymorphic association
20 # added as a quick fix to allow eager loading of the polymorphic association
21 # since always associated to an issue, for now
21 # since always associated to an issue, for now
22 belongs_to :issue, :foreign_key => :journalized_id
22 belongs_to :issue, :foreign_key => :journalized_id
23
23
24 belongs_to :user
24 belongs_to :user
25 has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
25 has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
26
26
27 acts_as_searchable :columns => 'notes',
27 acts_as_searchable :columns => 'notes',
28 :include => :issue,
28 :include => :issue,
29 :project_key => "#{Issue.table_name}.project_id",
29 :project_key => "#{Issue.table_name}.project_id",
30 :date_column => "#{Issue.table_name}.created_on"
30 :date_column => "#{Issue.table_name}.created_on"
31
31
32 acts_as_event :title => Proc.new {|o| "#{o.issue.tracker.name} ##{o.issue.id}: #{o.issue.subject}"},
33 :description => :notes,
34 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id}}
35
32 def save
36 def save
33 # Do not save an empty journal
37 # Do not save an empty journal
34 (details.empty? && notes.blank?) ? false : super
38 (details.empty? && notes.blank?) ? false : super
35 end
39 end
36 end
40 end
@@ -1,18 +1,18
1 <% if authorize_for('projects', 'add_issue') %>
1 <% if authorize_for('projects', 'add_issue') %>
2 <h3><%= l(:label_issue_new) %></h3>
2 <h3><%= l(:label_issue_new) %></h3>
3 <%= l(:label_tracker) %>: <%= new_issue_selector %>
3 <%= l(:label_tracker) %>: <%= new_issue_selector %>
4 <% end %>
4 <% end %>
5
5
6 <h3><%= l(:label_issue_plural) %></h3>
6 <h3><%= l(:label_issue_plural) %></h3>
7 <%= link_to l(:label_issue_view_all), :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 %><br />
7 <%= link_to l(:label_issue_view_all), { :set_filter => 1 } %><br />
8 <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
8 <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
9 <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
9 <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
10
10
11 <h3><%= l(:label_query_plural) %></h3>
11 <h3><%= l(:label_query_plural) %></h3>
12
12
13 <% queries = @project.queries.find(:all,
13 <% queries = @project.queries.find(:all,
14 :order => "name ASC",
14 :order => "name ASC",
15 :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
15 :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
16 queries.each do |query| %>
16 queries.each do |query| %>
17 <%= link_to query.name, :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => query %><br />
17 <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br />
18 <% end %>
18 <% end %>
@@ -1,40 +1,40
1 <% back_to = url_for(:controller => 'projects', :action => 'list_issues', :id => @project) %>
1 <% back_to = url_for(:controller => 'issues', :action => 'index', :project_id => @project) %>
2 <ul>
2 <ul>
3 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
3 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
4 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
4 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
5 <li class="folder">
5 <li class="folder">
6 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
6 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
7 <ul>
7 <ul>
8 <% @statuses.each do |s| %>
8 <% @statuses.each do |s| %>
9 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'change_status', :id => @issue, :new_status_id => s},
9 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'change_status', :id => @issue, :new_status_id => s},
10 :selected => (s == @issue.status), :disabled => !(@can[:change_status] && @allowed_statuses.include?(s)) %></li>
10 :selected => (s == @issue.status), :disabled => !(@can[:change_status] && @allowed_statuses.include?(s)) %></li>
11 <% end %>
11 <% end %>
12 </ul>
12 </ul>
13 </li>
13 </li>
14 <li class="folder">
14 <li class="folder">
15 <a href="#" class="submenu"><%= l(:field_priority) %></a>
15 <a href="#" class="submenu"><%= l(:field_priority) %></a>
16 <ul>
16 <ul>
17 <% @priorities.each do |p| %>
17 <% @priorities.each do |p| %>
18 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => back_to}, :method => :post,
18 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => back_to}, :method => :post,
19 :selected => (p == @issue.priority), :disabled => !@can[:edit] %></li>
19 :selected => (p == @issue.priority), :disabled => !@can[:edit] %></li>
20 <% end %>
20 <% end %>
21 </ul>
21 </ul>
22 </li>
22 </li>
23 <li class="folder">
23 <li class="folder">
24 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
24 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
25 <ul>
25 <ul>
26 <% @assignables.each do |u| %>
26 <% @assignables.each do |u| %>
27 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => u, :back_to => back_to}, :method => :post,
27 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => u, :back_to => back_to}, :method => :post,
28 :selected => (u == @issue.assigned_to), :disabled => !(@can[:edit] || @can[:change_status]) %></li>
28 :selected => (u == @issue.assigned_to), :disabled => !(@can[:edit] || @can[:change_status]) %></li>
29 <% end %>
29 <% end %>
30 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => back_to}, :method => :post,
30 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => back_to}, :method => :post,
31 :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li>
31 :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li>
32 </ul>
32 </ul>
33 </li>
33 </li>
34 <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},
34 <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},
35 :class => 'icon-copy', :disabled => !@can[:add] %></li>
35 :class => 'icon-copy', :disabled => !@can[:add] %></li>
36 <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
36 <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
37 :class => 'icon-move', :disabled => !@can[:move] %>
37 :class => 'icon-move', :disabled => !@can[:move] %>
38 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
38 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
39 :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
39 :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
40 </ul>
40 </ul>
@@ -1,49 +1,50
1 <% pdf=IfpdfHelper::IFPDF.new(current_language)
1 <% pdf=IfpdfHelper::IFPDF.new(current_language)
2 pdf.SetTitle("#{@project.name} - #{l(:label_issue_plural)}")
2 title = @project ? "#{@project.name} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
3 pdf.SetTitle(title)
3 pdf.AliasNbPages
4 pdf.AliasNbPages
4 pdf.footer_date = format_date(Date.today)
5 pdf.footer_date = format_date(Date.today)
5 pdf.AddPage("L")
6 pdf.AddPage("L")
6 row_height = 7
7 row_height = 7
7
8
8 #
9 #
9 # title
10 # title
10 #
11 #
11 pdf.SetFontStyle('B',11)
12 pdf.SetFontStyle('B',11)
12 pdf.Cell(190,10, "#{@project.name} - #{l(:label_issue_plural)}")
13 pdf.Cell(190,10, title)
13 pdf.Ln
14 pdf.Ln
14
15
15 #
16 #
16 # headers
17 # headers
17 #
18 #
18 pdf.SetFontStyle('B',10)
19 pdf.SetFontStyle('B',10)
19 pdf.SetFillColor(230, 230, 230)
20 pdf.SetFillColor(230, 230, 230)
20 pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
21 pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
21 pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
22 pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
22 pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
23 pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
23 pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
24 pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
24 pdf.Cell(40, row_height, l(:field_author), 0, 0, 'L', 1)
25 pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
25 pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
26 pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
26 pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
27 pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
27 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
28 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
28 pdf.Ln
29 pdf.Ln
29 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
30 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
30 pdf.SetY(pdf.GetY() + 1)
31 pdf.SetY(pdf.GetY() + 1)
31
32
32 #
33 #
33 # rows
34 # rows
34 #
35 #
35 pdf.SetFontStyle('',9)
36 pdf.SetFontStyle('',9)
36 pdf.SetFillColor(255, 255, 255)
37 pdf.SetFillColor(255, 255, 255)
37 @issues.each do |issue|
38 @issues.each do |issue|
38 pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
39 pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
39 pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
40 pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
40 pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
41 pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
41 pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
42 pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
42 pdf.Cell(40, row_height, issue.author.name, 0, 0, 'L', 1)
43 pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.name : '', 0, 0, 'L', 1)
43 pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
44 pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
44 pdf.MultiCell(0, row_height, (@project == issue.project ? issue.subject : "#{issue.project.name} - #{issue.subject}"))
45 pdf.MultiCell(0, row_height, (@project == issue.project ? issue.subject : "#{issue.project.name} - #{issue.subject}"))
45 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
46 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
46 pdf.SetY(pdf.GetY() + 1)
47 pdf.SetY(pdf.GetY() + 1)
47 end
48 end
48 %>
49 %>
49 <%= pdf.Output %> No newline at end of file
50 <%= pdf.Output %>
@@ -1,35 +1,72
1 <% if @query.new_record? %>
1 <h2><%=l(:label_issue_plural)%></h2>
2 <h2><%=l(:label_issue_plural)%></h2>
3 <% set_html_title l(:label_issue_plural) %>
2
4
3 <% form_tag({}, :id => 'query_form') do %>
5 <% form_tag({ :controller => 'queries', :action => 'new', :project_id => @project }, :id => 'query_form') do %>
4 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
6 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
5 <% end %>
6 <div class="contextual">
7 <div class="contextual">
7 <%= link_to_remote l(:button_apply),
8 <%= link_to_remote l(:button_apply),
8 { :url => { :set_filter => 1 },
9 { :url => { :set_filter => 1 },
9 :update => "content",
10 :update => "content",
10 :with => "Form.serialize('query_form')"
11 :with => "Form.serialize('query_form')"
11 }, :class => 'icon icon-edit' %>
12 }, :class => 'icon icon-edit' %>
12
13
13 <%= link_to_remote l(:button_clear),
14 <%= link_to_remote l(:button_clear),
14 { :url => { :set_filter => 1 },
15 { :url => { :set_filter => 1 },
15 :update => "content",
16 :update => "content",
16 }, :class => 'icon icon-reload' %>
17 }, :class => 'icon icon-reload' %>
18
19 <% if current_role && current_role.allowed_to?(:save_queries) %>
20 <%= link_to l(:button_save), {}, :onclick => "$('query_form').submit(); return false;", :class => 'icon icon-save' %>
21 <% end %>
22 </div>
23 <br />
24 &nbsp;
25 <% end %>
26 <% else %>
27 <div class="contextual">
28 <% if @query.editable_by?(User.current) %>
29 <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'icon icon-edit' %>
30 <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
31 <% end %>
17 </div>
32 </div>
18 <br />&nbsp;
19
33
34 <h2><%= @query.name %></h2>
35 <div id="query_form"></div>
36 <% set_html_title @query.name %>
37 <% end %>
20 <%= error_messages_for 'query' %>
38 <%= error_messages_for 'query' %>
21 <% if @query.valid? %>
39 <% if @query.valid? %>
22 <% if @issues.empty? %>
40 <% if @issues.empty? %>
23 <p><i><%= l(:label_no_data) %></i></p>
41 <p class="nodata"><%= l(:label_no_data) %></p>
24 <% else %>
42 <% else %>
25 &nbsp;
43 <% form_tag({:controller => 'projects', :action => 'bulk_edit_issues', :id => @project}, :id => 'issues_form', :onsubmit => "if (!checkBulkEdit(this)) {alert('#{l(:notice_no_issue_selected)}'); return false;}" ) do %>
26 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
44 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
27
45 <div class="contextual">
46 <%= l(:label_export_to) %>
47 <%= link_to 'CSV', {:format => 'csv'}, :class => 'icon icon-csv' %>,
48 <%= link_to 'PDF', {:format => 'pdf'}, :class => 'icon icon-pdf' %>
49 </div>
28 <p><%= pagination_links_full @issue_pages %>
50 <p><%= pagination_links_full @issue_pages %>
29 [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]</p>
51 [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]</p>
30 <% end %>
52 <% end %>
31 <% end %>
53 <% end %>
54 <% end %>
55
56 <% content_for :sidebar do %>
57 <%= render :partial => 'issues/sidebar' %>
58 <% end if @project%>
32
59
33 <% content_for :header_tags do %>
60 <% content_for :header_tags do %>
34 <%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
61 <%= auto_discovery_link_tag(:atom, {:query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_issue_plural)) %>
62 <%= auto_discovery_link_tag(:atom, {:action => 'changes', :query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_changes_details)) %>
63 <%= javascript_include_tag 'calendar/calendar' %>
64 <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
65 <%= javascript_include_tag 'calendar/calendar-setup' %>
66 <%= stylesheet_link_tag 'calendar' %>
67 <%= javascript_include_tag 'context_menu' %>
68 <%= stylesheet_link_tag 'context_menu' %>
35 <% end %>
69 <% end %>
70
71 <div id="context-menu" style="display: none;"></div>
72 <%= javascript_tag 'new ContextMenu({})' %>
@@ -1,73 +1,73
1 <h2><%=l(:label_roadmap)%></h2>
1 <h2><%=l(:label_roadmap)%></h2>
2
2
3 <% if @versions.empty? %>
3 <% if @versions.empty? %>
4 <p class="nodata"><%= l(:label_no_data) %></p>
4 <p class="nodata"><%= l(:label_no_data) %></p>
5 <% end %>
5 <% end %>
6
6
7 <% @versions.each do |version| %>
7 <% @versions.each do |version| %>
8 <a name="<%= version.name %>"><h3 class="icon22 icon22-package"><%= version.name %></h3></a>
8 <a name="<%= version.name %>"><h3 class="icon22 icon22-package"><%= version.name %></h3></a>
9 <% if version.completed? %>
9 <% if version.completed? %>
10 <p><%= format_date(version.effective_date) %></p>
10 <p><%= format_date(version.effective_date) %></p>
11 <% elsif version.overdue? %>
11 <% elsif version.overdue? %>
12 <p><strong><%= l(:label_roadmap_overdue, distance_of_time_in_words(Time.now, version.effective_date)) %> (<%= format_date(version.effective_date) %>)</strong></p>
12 <p><strong><%= l(:label_roadmap_overdue, distance_of_time_in_words(Time.now, version.effective_date)) %> (<%= format_date(version.effective_date) %>)</strong></p>
13 <% elsif version.effective_date %>
13 <% elsif version.effective_date %>
14 <p><strong><%=l(:label_roadmap_due_in)%> <%= distance_of_time_in_words Time.now, version.effective_date %> (<%= format_date(version.effective_date) %>)</strong></p>
14 <p><strong><%=l(:label_roadmap_due_in)%> <%= distance_of_time_in_words Time.now, version.effective_date %> (<%= format_date(version.effective_date) %>)</strong></p>
15 <% end %>
15 <% end %>
16 <p><%=h version.description %></p>
16 <p><%=h version.description %></p>
17 <% issues = version.fixed_issues.find(:all,
17 <% issues = version.fixed_issues.find(:all,
18 :include => [:status, :tracker],
18 :include => [:status, :tracker],
19 :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
19 :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
20 :order => "#{Tracker.table_name}.position") unless @selected_tracker_ids.empty?
20 :order => "#{Tracker.table_name}.position") unless @selected_tracker_ids.empty?
21 issues ||= []
21 issues ||= []
22
22
23 total = issues.size
23 total = issues.size
24 complete = issues.inject(0) {|c,i| i.status.is_closed? ? c + 1 : c }
24 complete = issues.inject(0) {|c,i| i.status.is_closed? ? c + 1 : c }
25 percentComplete = total == 0 ? 100 : (100.0 / total * complete).floor
25 percentComplete = total == 0 ? 100 : (100.0 / total * complete).floor
26 percentIncomplete = 100 - percentComplete
26 percentIncomplete = 100 - percentComplete
27 %>
27 %>
28 <table class="progress">
28 <table class="progress">
29 <tr>
29 <tr>
30 <% if percentComplete > 0 %>
30 <% if percentComplete > 0 %>
31 <td class="closed" style="width: <%= percentComplete %>%"></td>
31 <td class="closed" style="width: <%= percentComplete %>%"></td>
32 <% end; if percentIncomplete > 0 %>
32 <% end; if percentIncomplete > 0 %>
33 <td class="open" style="width: <%= percentIncomplete %>%"></td>
33 <td class="open" style="width: <%= percentIncomplete %>%"></td>
34 <% end %>
34 <% end %>
35 </tr>
35 </tr>
36 </table>
36 </table>
37 <em><%= link_to(complete, :controller => 'projects', :action => 'list_issues', :id => @project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %> <%= lwr(:label_closed_issues, complete) %> (<%= percentComplete %>%) &#160;
37 <em><%= link_to(complete, :controller => 'issues', :action => 'index', :project_id => @project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %> <%= lwr(:label_closed_issues, complete) %> (<%= percentComplete %>%) &#160;
38 <%= link_to((total - complete), :controller => 'projects', :action => 'list_issues', :id => @project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %> <%= lwr(:label_open_issues, total - complete)%> (<%= percentIncomplete %>%)</em>
38 <%= link_to((total - complete), :controller => 'issues', :action => 'index', :project_id => @project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %> <%= lwr(:label_open_issues, total - complete)%> (<%= percentIncomplete %>%)</em>
39 <br />
39 <br />
40 <br />
40 <br />
41 <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
41 <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
42 <ul>
42 <ul>
43 <% if total == 0 %>
43 <% if total == 0 %>
44 <li><%=l(:label_roadmap_no_issues)%></li>
44 <li><%=l(:label_roadmap_no_issues)%></li>
45 <% else %>
45 <% else %>
46 <% issues.each do |issue| %>
46 <% issues.each do |issue| %>
47 <li>
47 <li>
48 <%= link = link_to_issue(issue)
48 <%= link = link_to_issue(issue)
49 issue.status.is_closed? ? content_tag("del", link) : link %>: <%=h issue.subject %>
49 issue.status.is_closed? ? content_tag("del", link) : link %>: <%=h issue.subject %>
50 <%= content_tag "em", "(#{l(:label_closed_issues)})" if issue.status.is_closed? %>
50 <%= content_tag "em", "(#{l(:label_closed_issues)})" if issue.status.is_closed? %>
51 </li>
51 </li>
52 <% end %>
52 <% end %>
53 <% end %>
53 <% end %>
54 </ul>
54 </ul>
55 <% end %>
55 <% end %>
56
56
57 <% content_for :sidebar do %>
57 <% content_for :sidebar do %>
58 <% form_tag do %>
58 <% form_tag do %>
59 <h3><%= l(:label_roadmap) %></h3>
59 <h3><%= l(:label_roadmap) %></h3>
60 <% @trackers.each do |tracker| %>
60 <% @trackers.each do |tracker| %>
61 <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %>
61 <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %>
62 <%= tracker.name %></label><br />
62 <%= tracker.name %></label><br />
63 <% end %>
63 <% end %>
64 <br />
64 <br />
65 <label for="completed"><%= check_box_tag "completed", 1, params[:completed] %> <%= l(:label_show_completed_versions) %>
65 <label for="completed"><%= check_box_tag "completed", 1, params[:completed] %> <%= l(:label_show_completed_versions) %>
66 <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
66 <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
67 <% end %>
67 <% end %>
68
68
69 <h3><%= l(:label_version_plural) %></h3>
69 <h3><%= l(:label_version_plural) %></h3>
70 <% @versions.each do |version| %>
70 <% @versions.each do |version| %>
71 <%= link_to version.name, :anchor => version.name %><br />
71 <%= link_to version.name, :anchor => version.name %><br />
72 <% end %>
72 <% end %>
73 <% end %>
73 <% end %>
@@ -1,83 +1,83
1 <h2><%=l(:label_overview)%></h2>
1 <h2><%=l(:label_overview)%></h2>
2
2
3 <div class="splitcontentleft">
3 <div class="splitcontentleft">
4 <%= textilizable @project.description %>
4 <%= textilizable @project.description %>
5 <ul>
5 <ul>
6 <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link @project.homepage %></li><% end %>
6 <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link @project.homepage %></li><% end %>
7 <% if @subprojects.any? %>
7 <% if @subprojects.any? %>
8 <li><%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(p.name, :action => 'show', :id => p)}.join(", ") %></li>
8 <li><%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(p.name, :action => 'show', :id => p)}.join(", ") %></li>
9 <% end %>
9 <% end %>
10 <% if @project.parent %>
10 <% if @project.parent %>
11 <li><%=l(:field_parent)%>: <%= link_to @project.parent.name, :controller => 'projects', :action => 'show', :id => @project.parent %></li>
11 <li><%=l(:field_parent)%>: <%= link_to @project.parent.name, :controller => 'projects', :action => 'show', :id => @project.parent %></li>
12 <% end %>
12 <% end %>
13 <% for custom_value in @custom_values %>
13 <% for custom_value in @custom_values %>
14 <% if !custom_value.value.empty? %>
14 <% if !custom_value.value.empty? %>
15 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
15 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
16 <% end %>
16 <% end %>
17 <% end %>
17 <% end %>
18 </ul>
18 </ul>
19
19
20 <% if User.current.allowed_to?(:view_issues, @project) %>
20 <% if User.current.allowed_to?(:view_issues, @project) %>
21 <div class="box">
21 <div class="box">
22 <h3 class="icon22 icon22-tracker"><%=l(:label_issue_tracking)%></h3>
22 <h3 class="icon22 icon22-tracker"><%=l(:label_issue_tracking)%></h3>
23 <ul>
23 <ul>
24 <% for tracker in @trackers %>
24 <% for tracker in @trackers %>
25 <li><%= link_to tracker.name, :controller => 'projects', :action => 'list_issues', :id => @project,
25 <li><%= link_to tracker.name, :controller => 'issues', :action => 'index', :project_id => @project,
26 :set_filter => 1,
26 :set_filter => 1,
27 "tracker_id" => tracker.id %>:
27 "tracker_id" => tracker.id %>:
28 <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
28 <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
29 <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
29 <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
30 <% end %>
30 <% end %>
31 </ul>
31 </ul>
32 <p><%= link_to l(:label_issue_view_all), :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 %></p>
32 <p><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %></p>
33 </div>
33 </div>
34 <% end %>
34 <% end %>
35 </div>
35 </div>
36
36
37 <div class="splitcontentright">
37 <div class="splitcontentright">
38 <% if @members_by_role.any? %>
38 <% if @members_by_role.any? %>
39 <div class="box">
39 <div class="box">
40 <h3 class="icon22 icon22-users"><%=l(:label_member_plural)%></h3>
40 <h3 class="icon22 icon22-users"><%=l(:label_member_plural)%></h3>
41 <p><% @members_by_role.keys.sort.each do |role| %>
41 <p><% @members_by_role.keys.sort.each do |role| %>
42 <%= role.name %>:
42 <%= role.name %>:
43 <%= @members_by_role[role].collect(&:user).sort.collect{|u| link_to_user u}.join(", ") %>
43 <%= @members_by_role[role].collect(&:user).sort.collect{|u| link_to_user u}.join(", ") %>
44 <br />
44 <br />
45 <% end %></p>
45 <% end %></p>
46 </div>
46 </div>
47 <% end %>
47 <% end %>
48
48
49 <% if @news.any? && authorize_for('projects', 'list_news') %>
49 <% if @news.any? && authorize_for('projects', 'list_news') %>
50 <div class="box">
50 <div class="box">
51 <h3><%=l(:label_news_latest)%></h3>
51 <h3><%=l(:label_news_latest)%></h3>
52 <%= render :partial => 'news/news', :collection => @news %>
52 <%= render :partial => 'news/news', :collection => @news %>
53 <p><%= link_to l(:label_news_view_all), :controller => 'projects', :action => 'list_news', :id => @project %></p>
53 <p><%= link_to l(:label_news_view_all), :controller => 'projects', :action => 'list_news', :id => @project %></p>
54 </div>
54 </div>
55 <% end %>
55 <% end %>
56 </div>
56 </div>
57
57
58 <% content_for :sidebar do %>
58 <% content_for :sidebar do %>
59 <% if authorize_for('projects', 'add_issue') %>
59 <% if authorize_for('projects', 'add_issue') %>
60 <h3><%= l(:label_issue_new) %></h3>
60 <h3><%= l(:label_issue_new) %></h3>
61 <%= l(:label_tracker) %>: <%= new_issue_selector %>
61 <%= l(:label_tracker) %>: <%= new_issue_selector %>
62 <% end %>
62 <% end %>
63
63
64 <% planning_links = []
64 <% planning_links = []
65 planning_links << link_to_if_authorized(l(:label_calendar), :action => 'calendar', :id => @project)
65 planning_links << link_to_if_authorized(l(:label_calendar), :action => 'calendar', :id => @project)
66 planning_links << link_to_if_authorized(l(:label_gantt), :action => 'gantt', :id => @project)
66 planning_links << link_to_if_authorized(l(:label_gantt), :action => 'gantt', :id => @project)
67 planning_links.compact!
67 planning_links.compact!
68 unless planning_links.empty? %>
68 unless planning_links.empty? %>
69 <h3>Planning</h3>
69 <h3>Planning</h3>
70 <p><%= planning_links.join(' | ') %></p>
70 <p><%= planning_links.join(' | ') %></p>
71 <% end %>
71 <% end %>
72
72
73 <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>
73 <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>
74 <h3><%= l(:label_spent_time) %></h3>
74 <h3><%= l(:label_spent_time) %></h3>
75 <p><span class="icon icon-time"><%= lwr(:label_f_hour, @total_hours) %></span></p>
75 <p><span class="icon icon-time"><%= lwr(:label_f_hour, @total_hours) %></span></p>
76 <p><%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}) %> |
76 <p><%= link_to(l(:label_details), {:controller => 'timelog', :action => 'details', :project_id => @project}) %> |
77 <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}) %></p>
77 <%= link_to(l(:label_report), {:controller => 'timelog', :action => 'report', :project_id => @project}) %></p>
78 <% end %>
78 <% end %>
79 <% end %>
79 <% end %>
80
80
81 <% content_for :header_tags do %>
81 <% content_for :header_tags do %>
82 <%= auto_discovery_link_tag(:atom, {:action => 'activity', :id => @project, :format => 'atom', :key => User.current.rss_key}) %>
82 <%= auto_discovery_link_tag(:atom, {:action => 'activity', :id => @project, :format => 'atom', :key => User.current.rss_key}) %>
83 <% end %>
83 <% end %>
@@ -1,27 +1,27
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to_if_authorized l(:label_query_new), {:controller => 'queries', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %>
2 <%= link_to_if_authorized l(:label_query_new), {:controller => 'queries', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %>
3 </div>
3 </div>
4
4
5 <h2><%= l(:label_query_plural) %></h2>
5 <h2><%= l(:label_query_plural) %></h2>
6
6
7 <% if @queries.empty? %>
7 <% if @queries.empty? %>
8 <p><i><%=l(:label_no_data)%></i></p>
8 <p><i><%=l(:label_no_data)%></i></p>
9 <% else %>
9 <% else %>
10 <table class="list">
10 <table class="list">
11 <% @queries.each do |query| %>
11 <% @queries.each do |query| %>
12 <tr class="<%= cycle('odd', 'even') %>">
12 <tr class="<%= cycle('odd', 'even') %>">
13 <td>
13 <td>
14 <%= link_to query.name, :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => query %>
14 <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %>
15 </td>
15 </td>
16 <td align="right">
16 <td align="right">
17 <small>
17 <small>
18 <% if query.editable_by?(User.current) %>
18 <% if query.editable_by?(User.current) %>
19 <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => query}, :class => 'icon icon-edit' %>
19 <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => query}, :class => 'icon icon-edit' %>
20 <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
20 <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
21 </small>
21 </small>
22 <% end %>
22 <% end %>
23 </td>
23 </td>
24 </tr>
24 </tr>
25 <% end %>
25 <% end %>
26 </table>
26 </table>
27 <% end %>
27 <% end %>
@@ -1,48 +1,48
1 <% if @statuses.empty? or rows.empty? %>
1 <% if @statuses.empty? or rows.empty? %>
2 <p><i><%=l(:label_no_data)%></i></p>
2 <p><i><%=l(:label_no_data)%></i></p>
3 <% else %>
3 <% else %>
4 <% col_width = 70 / (@statuses.length+3) %>
4 <% col_width = 70 / (@statuses.length+3) %>
5 <table class="list">
5 <table class="list">
6 <thead><tr>
6 <thead><tr>
7 <th style="width:25%"></th>
7 <th style="width:25%"></th>
8 <% for status in @statuses %>
8 <% for status in @statuses %>
9 <th style="width:<%= col_width %>%"><%= status.name %></th>
9 <th style="width:<%= col_width %>%"><%= status.name %></th>
10 <% end %>
10 <% end %>
11 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_open_issues_plural)%></strong></th>
11 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_open_issues_plural)%></strong></th>
12 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_closed_issues_plural)%></strong></th>
12 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_closed_issues_plural)%></strong></th>
13 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_total)%></strong></th>
13 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_total)%></strong></th>
14 </tr></thead>
14 </tr></thead>
15 <tbody>
15 <tbody>
16 <% for row in rows %>
16 <% for row in rows %>
17 <tr class="<%= cycle("odd", "even") %>">
17 <tr class="<%= cycle("odd", "even") %>">
18 <td><%= link_to row.name, :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
18 <td><%= link_to row.name, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
19 :set_filter => 1,
19 :set_filter => 1,
20 "#{field_name}" => row.id %></td>
20 "#{field_name}" => row.id %></td>
21 <% for status in @statuses %>
21 <% for status in @statuses %>
22 <td align="center"><%= aggregate_link data, { field_name => row.id, "status_id" => status.id },
22 <td align="center"><%= aggregate_link data, { field_name => row.id, "status_id" => status.id },
23 :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
23 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
24 :set_filter => 1,
24 :set_filter => 1,
25 "status_id" => status.id,
25 "status_id" => status.id,
26 "#{field_name}" => row.id %></td>
26 "#{field_name}" => row.id %></td>
27 <% end %>
27 <% end %>
28 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 0 },
28 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 0 },
29 :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
29 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
30 :set_filter => 1,
30 :set_filter => 1,
31 "#{field_name}" => row.id,
31 "#{field_name}" => row.id,
32 "status_id" => "o" %></td>
32 "status_id" => "o" %></td>
33 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 1 },
33 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 1 },
34 :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
34 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
35 :set_filter => 1,
35 :set_filter => 1,
36 "#{field_name}" => row.id,
36 "#{field_name}" => row.id,
37 "status_id" => "c" %></td>
37 "status_id" => "c" %></td>
38 <td align="center"><%= aggregate_link data, { field_name => row.id },
38 <td align="center"><%= aggregate_link data, { field_name => row.id },
39 :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
39 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
40 :set_filter => 1,
40 :set_filter => 1,
41 "#{field_name}" => row.id,
41 "#{field_name}" => row.id,
42 "status_id" => "*" %></td>
42 "status_id" => "*" %></td>
43 </tr>
43 </tr>
44 <% end %>
44 <% end %>
45 </tbody>
45 </tbody>
46 </table>
46 </table>
47 <% end
47 <% end
48 reset_cycle %> No newline at end of file
48 reset_cycle %>
@@ -1,37 +1,37
1 <% if @statuses.empty? or rows.empty? %>
1 <% if @statuses.empty? or rows.empty? %>
2 <p><i><%=l(:label_no_data)%></i></p>
2 <p><i><%=l(:label_no_data)%></i></p>
3 <% else %>
3 <% else %>
4 <table class="list">
4 <table class="list">
5 <thead><tr>
5 <thead><tr>
6 <th style="width:25%"></th>
6 <th style="width:25%"></th>
7 <th align="center" style="width:25%"><%=l(:label_open_issues_plural)%></th>
7 <th align="center" style="width:25%"><%=l(:label_open_issues_plural)%></th>
8 <th align="center" style="width:25%"><%=l(:label_closed_issues_plural)%></th>
8 <th align="center" style="width:25%"><%=l(:label_closed_issues_plural)%></th>
9 <th align="center" style="width:25%"><%=l(:label_total)%></th>
9 <th align="center" style="width:25%"><%=l(:label_total)%></th>
10 </tr></thead>
10 </tr></thead>
11 <tbody>
11 <tbody>
12 <% for row in rows %>
12 <% for row in rows %>
13 <tr class="<%= cycle("odd", "even") %>">
13 <tr class="<%= cycle("odd", "even") %>">
14 <td><%= link_to row.name, :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
14 <td><%= link_to row.name, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
15 :set_filter => 1,
15 :set_filter => 1,
16 "#{field_name}" => row.id %></td>
16 "#{field_name}" => row.id %></td>
17 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 0 },
17 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 0 },
18 :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
18 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
19 :set_filter => 1,
19 :set_filter => 1,
20 "#{field_name}" => row.id,
20 "#{field_name}" => row.id,
21 "status_id" => "o" %></td>
21 "status_id" => "o" %></td>
22 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 1 },
22 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 1 },
23 :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
23 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
24 :set_filter => 1,
24 :set_filter => 1,
25 "#{field_name}" => row.id,
25 "#{field_name}" => row.id,
26 "status_id" => "c" %></td>
26 "status_id" => "c" %></td>
27 <td align="center"><%= aggregate_link data, { field_name => row.id },
27 <td align="center"><%= aggregate_link data, { field_name => row.id },
28 :controller => 'projects', :action => 'list_issues', :id => ((row.is_a?(Project) ? row : @project)),
28 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
29 :set_filter => 1,
29 :set_filter => 1,
30 "#{field_name}" => row.id,
30 "#{field_name}" => row.id,
31 "status_id" => "*" %></td>
31 "status_id" => "*" %></td>
32 </tr>
32 </tr>
33 <% end %>
33 <% end %>
34 </tbody>
34 </tbody>
35 </table>
35 </table>
36 <% end
36 <% end
37 reset_cycle %> No newline at end of file
37 reset_cycle %>
@@ -1,88 +1,91
1 # Be sure to restart your web server when you modify this file.
1 # Be sure to restart your web server when you modify this file.
2
2
3 # Uncomment below to force Rails into production mode when
3 # Uncomment below to force Rails into production mode when
4 # you don't control web/app server and can't set it the proper way
4 # you don't control web/app server and can't set it the proper way
5 # ENV['RAILS_ENV'] ||= 'production'
5 # ENV['RAILS_ENV'] ||= 'production'
6
6
7 # Bootstrap the Rails environment, frameworks, and default configuration
7 # Bootstrap the Rails environment, frameworks, and default configuration
8 require File.join(File.dirname(__FILE__), 'boot')
8 require File.join(File.dirname(__FILE__), 'boot')
9
9
10 Rails::Initializer.run do |config|
10 Rails::Initializer.run do |config|
11 # Settings in config/environments/* take precedence those specified here
11 # Settings in config/environments/* take precedence those specified here
12
12
13 # Skip frameworks you're not going to use
13 # Skip frameworks you're not going to use
14 # config.frameworks -= [ :action_web_service, :action_mailer ]
14 # config.frameworks -= [ :action_web_service, :action_mailer ]
15
15
16 # Add additional load paths for sweepers
16 # Add additional load paths for sweepers
17 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
17 config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
18
18
19 # Force all environments to use the same logger level
19 # Force all environments to use the same logger level
20 # (by default production uses :info, the others :debug)
20 # (by default production uses :info, the others :debug)
21 # config.log_level = :debug
21 # config.log_level = :debug
22
22
23 # Use the database for sessions instead of the file system
23 # Use the database for sessions instead of the file system
24 # (create the session table with 'rake create_sessions_table')
24 # (create the session table with 'rake create_sessions_table')
25 # config.action_controller.session_store = :active_record_store
25 # config.action_controller.session_store = :active_record_store
26
26
27 # Enable page/fragment caching by setting a file-based store
27 # Enable page/fragment caching by setting a file-based store
28 # (remember to create the caching directory and make it readable to the application)
28 # (remember to create the caching directory and make it readable to the application)
29 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
29 # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
30
30
31 # Activate observers that should always be running
31 # Activate observers that should always be running
32 # config.active_record.observers = :cacher, :garbage_collector
32 # config.active_record.observers = :cacher, :garbage_collector
33 config.active_record.observers = :message_observer
33 config.active_record.observers = :message_observer
34
34
35 # Make Active Record use UTC-base instead of local time
35 # Make Active Record use UTC-base instead of local time
36 # config.active_record.default_timezone = :utc
36 # config.active_record.default_timezone = :utc
37
37
38 # Use Active Record's schema dumper instead of SQL when creating the test database
38 # Use Active Record's schema dumper instead of SQL when creating the test database
39 # (enables use of different database adapters for development and test environments)
39 # (enables use of different database adapters for development and test environments)
40 # config.active_record.schema_format = :ruby
40 # config.active_record.schema_format = :ruby
41
41
42 # See Rails::Configuration for more options
42 # See Rails::Configuration for more options
43
43
44 # SMTP server configuration
44 # SMTP server configuration
45 config.action_mailer.smtp_settings = {
45 config.action_mailer.smtp_settings = {
46 :address => "127.0.0.1",
46 :address => "127.0.0.1",
47 :port => 25,
47 :port => 25,
48 :domain => "somenet.foo",
48 :domain => "somenet.foo",
49 :authentication => :login,
49 :authentication => :login,
50 :user_name => "redmine",
50 :user_name => "redmine",
51 :password => "redmine",
51 :password => "redmine",
52 }
52 }
53
53
54 config.action_mailer.perform_deliveries = true
54 config.action_mailer.perform_deliveries = true
55
55
56 # Tell ActionMailer not to deliver emails to the real world.
56 # Tell ActionMailer not to deliver emails to the real world.
57 # The :test delivery method accumulates sent emails in the
57 # The :test delivery method accumulates sent emails in the
58 # ActionMailer::Base.deliveries array.
58 # ActionMailer::Base.deliveries array.
59 #config.action_mailer.delivery_method = :test
59 #config.action_mailer.delivery_method = :test
60 config.action_mailer.delivery_method = :smtp
60 config.action_mailer.delivery_method = :smtp
61
61
62 end
62 end
63
63
64 ActiveRecord::Errors.default_error_messages = {
64 ActiveRecord::Errors.default_error_messages = {
65 :inclusion => "activerecord_error_inclusion",
65 :inclusion => "activerecord_error_inclusion",
66 :exclusion => "activerecord_error_exclusion",
66 :exclusion => "activerecord_error_exclusion",
67 :invalid => "activerecord_error_invalid",
67 :invalid => "activerecord_error_invalid",
68 :confirmation => "activerecord_error_confirmation",
68 :confirmation => "activerecord_error_confirmation",
69 :accepted => "activerecord_error_accepted",
69 :accepted => "activerecord_error_accepted",
70 :empty => "activerecord_error_empty",
70 :empty => "activerecord_error_empty",
71 :blank => "activerecord_error_blank",
71 :blank => "activerecord_error_blank",
72 :too_long => "activerecord_error_too_long",
72 :too_long => "activerecord_error_too_long",
73 :too_short => "activerecord_error_too_short",
73 :too_short => "activerecord_error_too_short",
74 :wrong_length => "activerecord_error_wrong_length",
74 :wrong_length => "activerecord_error_wrong_length",
75 :taken => "activerecord_error_taken",
75 :taken => "activerecord_error_taken",
76 :not_a_number => "activerecord_error_not_a_number"
76 :not_a_number => "activerecord_error_not_a_number"
77 }
77 }
78
78
79 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
79 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
80
80
81 Mime::Type.register 'text/csv', :csv
82 Mime::Type.register 'application/pdf', :pdf
83
81 GLoc.set_config :default_language => :en
84 GLoc.set_config :default_language => :en
82 GLoc.clear_strings
85 GLoc.clear_strings
83 GLoc.set_kcode
86 GLoc.set_kcode
84 GLoc.load_localized_strings
87 GLoc.load_localized_strings
85 GLoc.set_config(:raise_string_not_found_errors => false)
88 GLoc.set_config(:raise_string_not_found_errors => false)
86
89
87 require 'redmine'
90 require 'redmine'
88
91
@@ -1,34 +1,35
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome'
9 map.home '', :controller => 'welcome'
10
10
11 map.connect 'wiki/:id/:page/:action', :controller => 'wiki', :page => nil
11 map.connect 'wiki/:id/:page/:action', :controller => 'wiki', :page => nil
12 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
12 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
13 map.connect 'help/:ctrl/:page', :controller => 'help'
13 map.connect 'help/:ctrl/:page', :controller => 'help'
14 #map.connect ':controller/:action/:id/:sort_key/:sort_order'
14 #map.connect ':controller/:action/:id/:sort_key/:sort_order'
15
15
16 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
16 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
17 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
17 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
18 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
18 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
19 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
19
20
20 map.with_options :controller => 'repositories' do |omap|
21 map.with_options :controller => 'repositories' do |omap|
21 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
22 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
22 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
23 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
23 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
24 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
24 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
25 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
25 end
26 end
26
27
27 # Allow downloading Web Service WSDL as a file with an extension
28 # Allow downloading Web Service WSDL as a file with an extension
28 # instead of a file named 'wsdl'
29 # instead of a file named 'wsdl'
29 map.connect ':controller/service.wsdl', :action => 'wsdl'
30 map.connect ':controller/service.wsdl', :action => 'wsdl'
30
31
31
32
32 # Install the default route as the lowest priority.
33 # Install the default route as the lowest priority.
33 map.connect ':controller/:action/:id'
34 map.connect ':controller/:action/:id'
34 end
35 end
@@ -1,103 +1,103
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/mime_type'
3 require 'redmine/mime_type'
4 require 'redmine/themes'
4 require 'redmine/themes'
5 require 'redmine/plugin'
5 require 'redmine/plugin'
6
6
7 begin
7 begin
8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 rescue LoadError
9 rescue LoadError
10 # RMagick is not available
10 # RMagick is not available
11 end
11 end
12
12
13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
14
14
15 # Permissions
15 # Permissions
16 Redmine::AccessControl.map do |map|
16 Redmine::AccessControl.map do |map|
17 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
17 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
18 map.permission :search_project, {:search => :index}, :public => true
18 map.permission :search_project, {:search => :index}, :public => true
19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23
23
24 map.project_module :issue_tracking do |map|
24 map.project_module :issue_tracking do |map|
25 # Issue categories
25 # Issue categories
26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 # Issues
27 # Issues
28 map.permission :view_issues, {:projects => [:list_issues, :export_issues_csv, :export_issues_pdf, :changelog, :roadmap],
28 map.permission :view_issues, {:projects => [:changelog, :roadmap],
29 :issues => [:show, :context_menu],
29 :issues => [:index, :changes, :show, :context_menu],
30 :queries => :index,
30 :queries => :index,
31 :reports => :issue_report}, :public => true
31 :reports => :issue_report}, :public => true
32 map.permission :add_issues, {:projects => :add_issue}, :require => :loggedin
32 map.permission :add_issues, {:projects => :add_issue}, :require => :loggedin
33 map.permission :edit_issues, {:projects => :bulk_edit_issues,
33 map.permission :edit_issues, {:projects => :bulk_edit_issues,
34 :issues => [:edit, :destroy_attachment]}, :require => :loggedin
34 :issues => [:edit, :destroy_attachment]}, :require => :loggedin
35 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}, :require => :loggedin
35 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}, :require => :loggedin
36 map.permission :add_issue_notes, {:issues => :add_note}, :require => :loggedin
36 map.permission :add_issue_notes, {:issues => :add_note}, :require => :loggedin
37 map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin
37 map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin
38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
38 map.permission :move_issues, {:projects => :move_issues}, :require => :loggedin
39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
39 map.permission :delete_issues, {:issues => :destroy}, :require => :member
40 # Queries
40 # Queries
41 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
41 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
42 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
43 # Gantt & calendar
43 # Gantt & calendar
44 map.permission :view_gantt, :projects => :gantt
44 map.permission :view_gantt, :projects => :gantt
45 map.permission :view_calendar, :projects => :calendar
45 map.permission :view_calendar, :projects => :calendar
46 end
46 end
47
47
48 map.project_module :time_tracking do |map|
48 map.project_module :time_tracking do |map|
49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
49 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
50 map.permission :view_time_entries, :timelog => [:details, :report]
50 map.permission :view_time_entries, :timelog => [:details, :report]
51 end
51 end
52
52
53 map.project_module :news do |map|
53 map.project_module :news do |map|
54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
54 map.permission :manage_news, {:projects => :add_news, :news => [:edit, :destroy, :destroy_comment]}, :require => :member
55 map.permission :view_news, {:projects => :list_news, :news => :show}, :public => true
55 map.permission :view_news, {:projects => :list_news, :news => :show}, :public => true
56 map.permission :comment_news, {:news => :add_comment}, :require => :loggedin
56 map.permission :comment_news, {:news => :add_comment}, :require => :loggedin
57 end
57 end
58
58
59 map.project_module :documents do |map|
59 map.project_module :documents do |map|
60 map.permission :manage_documents, {:projects => :add_document, :documents => [:edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
60 map.permission :manage_documents, {:projects => :add_document, :documents => [:edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
61 map.permission :view_documents, :projects => :list_documents, :documents => [:show, :download]
61 map.permission :view_documents, :projects => :list_documents, :documents => [:show, :download]
62 end
62 end
63
63
64 map.project_module :files do |map|
64 map.project_module :files do |map|
65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
65 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
66 map.permission :view_files, :projects => :list_files, :versions => :download
66 map.permission :view_files, :projects => :list_files, :versions => :download
67 end
67 end
68
68
69 map.project_module :wiki do |map|
69 map.project_module :wiki do |map|
70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
70 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
71 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
72 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
73 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
74 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
75 end
75 end
76
76
77 map.project_module :repository do |map|
77 map.project_module :repository do |map|
78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
78 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :changes, :diff, :stats, :graph]
79 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :changes, :diff, :stats, :graph]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
80 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
81 end
81 end
82
82
83 map.project_module :boards do |map|
83 map.project_module :boards do |map|
84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
84 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
85 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
86 map.permission :add_messages, {:messages => [:new, :reply]}, :require => :loggedin
86 map.permission :add_messages, {:messages => [:new, :reply]}, :require => :loggedin
87 end
87 end
88 end
88 end
89
89
90 # Project menu configuration
90 # Project menu configuration
91 Redmine::MenuManager.map :project_menu do |menu|
91 Redmine::MenuManager.map :project_menu do |menu|
92 menu.push :label_overview, :controller => 'projects', :action => 'show'
92 menu.push :label_overview, :controller => 'projects', :action => 'show'
93 menu.push :label_activity, :controller => 'projects', :action => 'activity'
93 menu.push :label_activity, :controller => 'projects', :action => 'activity'
94 menu.push :label_roadmap, :controller => 'projects', :action => 'roadmap'
94 menu.push :label_roadmap, :controller => 'projects', :action => 'roadmap'
95 menu.push :label_issue_plural, :controller => 'projects', :action => 'list_issues'
95 menu.push :label_issue_plural, { :controller => 'issues', :action => 'index' }, :param => :project_id
96 menu.push :label_news_plural, :controller => 'projects', :action => 'list_news'
96 menu.push :label_news_plural, :controller => 'projects', :action => 'list_news'
97 menu.push :label_document_plural, :controller => 'projects', :action => 'list_documents'
97 menu.push :label_document_plural, :controller => 'projects', :action => 'list_documents'
98 menu.push :label_wiki, { :controller => 'wiki', :action => 'index', :page => nil }, :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
98 menu.push :label_wiki, { :controller => 'wiki', :action => 'index', :page => nil }, :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
99 menu.push :label_board_plural, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, :if => Proc.new { |p| p.boards.any? }
99 menu.push :label_board_plural, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, :if => Proc.new { |p| p.boards.any? }
100 menu.push :label_attachment_plural, :controller => 'projects', :action => 'list_files'
100 menu.push :label_attachment_plural, :controller => 'projects', :action => 'list_files'
101 menu.push :label_repository, { :controller => 'repositories', :action => 'show' }, :if => Proc.new { |p| p.repository && !p.repository.new_record? }
101 menu.push :label_repository, { :controller => 'repositories', :action => 'show' }, :if => Proc.new { |p| p.repository && !p.repository.new_record? }
102 menu.push :label_settings, :controller => 'projects', :action => 'settings'
102 menu.push :label_settings, :controller => 'projects', :action => 'settings'
103 end
103 end
@@ -1,165 +1,138
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < Test::Unit::TestCase
24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :issues, :enabled_modules, :enumerations
25 fixtures :projects, :users, :roles, :members, :issues, :enabled_modules, :enumerations
26
26
27 def setup
27 def setup
28 @controller = ProjectsController.new
28 @controller = ProjectsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 end
31 end
32
32
33 def test_index
33 def test_index
34 get :index
34 get :index
35 assert_response :success
35 assert_response :success
36 assert_template 'list'
36 assert_template 'list'
37 end
37 end
38
38
39 def test_list
39 def test_list
40 get :list
40 get :list
41 assert_response :success
41 assert_response :success
42 assert_template 'list'
42 assert_template 'list'
43 assert_not_nil assigns(:project_tree)
43 assert_not_nil assigns(:project_tree)
44 end
44 end
45
45
46 def test_show
46 def test_show
47 get :show, :id => 1
47 get :show, :id => 1
48 assert_response :success
48 assert_response :success
49 assert_template 'show'
49 assert_template 'show'
50 assert_not_nil assigns(:project)
50 assert_not_nil assigns(:project)
51 end
51 end
52
52
53 def test_list_documents
53 def test_list_documents
54 get :list_documents, :id => 1
54 get :list_documents, :id => 1
55 assert_response :success
55 assert_response :success
56 assert_template 'list_documents'
56 assert_template 'list_documents'
57 assert_not_nil assigns(:grouped)
57 assert_not_nil assigns(:grouped)
58 end
58 end
59
59
60 def test_list_issues
61 get :list_issues, :id => 1
62 assert_response :success
63 assert_template 'list_issues'
64 assert_not_nil assigns(:issues)
65 end
66
67 def test_list_issues_with_filter
68 get :list_issues, :id => 1, :set_filter => 1
69 assert_response :success
70 assert_template 'list_issues'
71 assert_not_nil assigns(:issues)
72 end
73
74 def test_list_issues_reset_filter
75 post :list_issues, :id => 1
76 assert_response :success
77 assert_template 'list_issues'
78 assert_not_nil assigns(:issues)
79 end
80
81 def test_export_issues_csv
82 get :export_issues_csv, :id => 1
83 assert_response :success
84 assert_not_nil assigns(:issues)
85 end
86
87 def test_bulk_edit_issues
60 def test_bulk_edit_issues
88 @request.session[:user_id] = 2
61 @request.session[:user_id] = 2
89 # update issues priority
62 # update issues priority
90 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
63 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
91 assert_response 302
64 assert_response 302
92 # check that the issues were updated
65 # check that the issues were updated
93 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
66 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
94 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
67 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
95 end
68 end
96
69
97 def test_list_news
70 def test_list_news
98 get :list_news, :id => 1
71 get :list_news, :id => 1
99 assert_response :success
72 assert_response :success
100 assert_template 'list_news'
73 assert_template 'list_news'
101 assert_not_nil assigns(:newss)
74 assert_not_nil assigns(:newss)
102 end
75 end
103
76
104 def test_list_files
77 def test_list_files
105 get :list_files, :id => 1
78 get :list_files, :id => 1
106 assert_response :success
79 assert_response :success
107 assert_template 'list_files'
80 assert_template 'list_files'
108 assert_not_nil assigns(:versions)
81 assert_not_nil assigns(:versions)
109 end
82 end
110
83
111 def test_changelog
84 def test_changelog
112 get :changelog, :id => 1
85 get :changelog, :id => 1
113 assert_response :success
86 assert_response :success
114 assert_template 'changelog'
87 assert_template 'changelog'
115 assert_not_nil assigns(:versions)
88 assert_not_nil assigns(:versions)
116 end
89 end
117
90
118 def test_roadmap
91 def test_roadmap
119 get :roadmap, :id => 1
92 get :roadmap, :id => 1
120 assert_response :success
93 assert_response :success
121 assert_template 'roadmap'
94 assert_template 'roadmap'
122 assert_not_nil assigns(:versions)
95 assert_not_nil assigns(:versions)
123 end
96 end
124
97
125 def test_activity
98 def test_activity
126 get :activity, :id => 1
99 get :activity, :id => 1
127 assert_response :success
100 assert_response :success
128 assert_template 'activity'
101 assert_template 'activity'
129 assert_not_nil assigns(:events_by_day)
102 assert_not_nil assigns(:events_by_day)
130 end
103 end
131
104
132 def test_archive
105 def test_archive
133 @request.session[:user_id] = 1 # admin
106 @request.session[:user_id] = 1 # admin
134 post :archive, :id => 1
107 post :archive, :id => 1
135 assert_redirected_to 'admin/projects'
108 assert_redirected_to 'admin/projects'
136 assert !Project.find(1).active?
109 assert !Project.find(1).active?
137 end
110 end
138
111
139 def test_unarchive
112 def test_unarchive
140 @request.session[:user_id] = 1 # admin
113 @request.session[:user_id] = 1 # admin
141 Project.find(1).archive
114 Project.find(1).archive
142 post :unarchive, :id => 1
115 post :unarchive, :id => 1
143 assert_redirected_to 'admin/projects'
116 assert_redirected_to 'admin/projects'
144 assert Project.find(1).active?
117 assert Project.find(1).active?
145 end
118 end
146
119
147 def test_add_issue
120 def test_add_issue
148 @request.session[:user_id] = 2
121 @request.session[:user_id] = 2
149 get :add_issue, :id => 1, :tracker_id => 1
122 get :add_issue, :id => 1, :tracker_id => 1
150 assert_response :success
123 assert_response :success
151 assert_template 'add_issue'
124 assert_template 'add_issue'
152 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}
125 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}
153 assert_redirected_to 'projects/list_issues'
126 assert_redirected_to 'projects/1/issues'
154 assert Issue.find_by_subject('This is the test_add_issue issue')
127 assert Issue.find_by_subject('This is the test_add_issue issue')
155 end
128 end
156
129
157 def test_copy_issue
130 def test_copy_issue
158 @request.session[:user_id] = 2
131 @request.session[:user_id] = 2
159 get :add_issue, :id => 1, :copy_from => 1
132 get :add_issue, :id => 1, :copy_from => 1
160 assert_template 'add_issue'
133 assert_template 'add_issue'
161 assert_not_nil assigns(:issue)
134 assert_not_nil assigns(:issue)
162 orig = Issue.find(1)
135 orig = Issue.find(1)
163 assert_equal orig.subject, assigns(:issue).subject
136 assert_equal orig.subject, assigns(:issue).subject
164 end
137 end
165 end
138 end
@@ -1,58 +1,58
1 require "#{File.dirname(__FILE__)}/../test_helper"
1 require "#{File.dirname(__FILE__)}/../test_helper"
2
2
3 class IssuesTest < ActionController::IntegrationTest
3 class IssuesTest < ActionController::IntegrationTest
4 fixtures :projects, :users, :trackers, :issue_statuses, :issues, :enumerations
4 fixtures :projects, :users, :trackers, :issue_statuses, :issues, :enumerations
5
5
6 # create an issue
6 # create an issue
7 def test_add_issue
7 def test_add_issue
8 log_user('jsmith', 'jsmith')
8 log_user('jsmith', 'jsmith')
9 get "projects/add_issue/1", :tracker_id => "1"
9 get "projects/add_issue/1", :tracker_id => "1"
10 assert_response :success
10 assert_response :success
11 assert_template "projects/add_issue"
11 assert_template "projects/add_issue"
12
12
13 post "projects/add_issue/1", :tracker_id => "1",
13 post "projects/add_issue/1", :tracker_id => "1",
14 :issue => { :start_date => "2006-12-26",
14 :issue => { :start_date => "2006-12-26",
15 :priority_id => "3",
15 :priority_id => "3",
16 :subject => "new test issue",
16 :subject => "new test issue",
17 :category_id => "",
17 :category_id => "",
18 :description => "new issue",
18 :description => "new issue",
19 :done_ratio => "0",
19 :done_ratio => "0",
20 :due_date => "",
20 :due_date => "",
21 :assigned_to_id => "" }
21 :assigned_to_id => "" }
22 # find created issue
22 # find created issue
23 issue = Issue.find_by_subject("new test issue")
23 issue = Issue.find_by_subject("new test issue")
24 assert_kind_of Issue, issue
24 assert_kind_of Issue, issue
25
25
26 # check redirection
26 # check redirection
27 assert_redirected_to "projects/list_issues/1"
27 assert_redirected_to "projects/1/issues"
28 follow_redirect!
28 follow_redirect!
29 assert assigns(:issues).include?(issue)
29 assert assigns(:issues).include?(issue)
30
30
31 # check issue attributes
31 # check issue attributes
32 assert_equal 'jsmith', issue.author.login
32 assert_equal 'jsmith', issue.author.login
33 assert_equal 1, issue.project.id
33 assert_equal 1, issue.project.id
34 assert_equal 1, issue.status.id
34 assert_equal 1, issue.status.id
35 end
35 end
36
36
37 # add then remove 2 attachments to an issue
37 # add then remove 2 attachments to an issue
38 def test_issue_attachements
38 def test_issue_attachements
39 log_user('jsmith', 'jsmith')
39 log_user('jsmith', 'jsmith')
40
40
41 post "issues/add_note/1", { :notes => 'Some notes', 'attachments[]' => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/testfile.txt', 'text/plain') }
41 post "issues/add_note/1", { :notes => 'Some notes', 'attachments[]' => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/testfile.txt', 'text/plain') }
42 assert_redirected_to "issues/show/1"
42 assert_redirected_to "issues/show/1"
43
43
44 # make sure attachment was saved
44 # make sure attachment was saved
45 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
45 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
46 assert_kind_of Attachment, attachment
46 assert_kind_of Attachment, attachment
47 assert_equal Issue.find(1), attachment.container
47 assert_equal Issue.find(1), attachment.container
48 # verify the size of the attachment stored in db
48 # verify the size of the attachment stored in db
49 #assert_equal file_data_1.length, attachment.filesize
49 #assert_equal file_data_1.length, attachment.filesize
50 # verify that the attachment was written to disk
50 # verify that the attachment was written to disk
51 assert File.exist?(attachment.diskfile)
51 assert File.exist?(attachment.diskfile)
52
52
53 # remove the attachments
53 # remove the attachments
54 Issue.find(1).attachments.each(&:destroy)
54 Issue.find(1).attachments.each(&:destroy)
55 assert_equal 0, Issue.find(1).attachments.length
55 assert_equal 0, Issue.find(1).attachments.length
56 end
56 end
57
57
58 end
58 end
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now