##// END OF EJS Templates
Performance improvement on calendar and gantt (about 45% on gantt for large number of issues)....
Jean-Philippe Lang -
r783:20b4e226fe06
parent child
Show More
@@ -1,628 +1,629
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'
18 require 'csv'
19
19
20 class ProjectsController < ApplicationController
20 class ProjectsController < ApplicationController
21 layout 'base'
21 layout 'base'
22 before_filter :find_project, :except => [ :index, :list, :add ]
22 before_filter :find_project, :except => [ :index, :list, :add ]
23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
25 accept_key_auth :activity, :calendar
25 accept_key_auth :activity, :calendar
26
26
27 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
27 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
28 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
28 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
29 cache_sweeper :version_sweeper, :only => [ :add_version ]
29 cache_sweeper :version_sweeper, :only => [ :add_version ]
30
30
31 helper :sort
31 helper :sort
32 include SortHelper
32 include SortHelper
33 helper :custom_fields
33 helper :custom_fields
34 include CustomFieldsHelper
34 include CustomFieldsHelper
35 helper :ifpdf
35 helper :ifpdf
36 include IfpdfHelper
36 include IfpdfHelper
37 helper :issues
37 helper IssuesHelper
38 helper IssuesHelper
38 helper :queries
39 helper :queries
39 include QueriesHelper
40 include QueriesHelper
40 helper :repositories
41 helper :repositories
41 include RepositoriesHelper
42 include RepositoriesHelper
42 include ProjectsHelper
43 include ProjectsHelper
43
44
44 def index
45 def index
45 list
46 list
46 render :action => 'list' unless request.xhr?
47 render :action => 'list' unless request.xhr?
47 end
48 end
48
49
49 # Lists visible projects
50 # Lists visible projects
50 def list
51 def list
51 projects = Project.find :all,
52 projects = Project.find :all,
52 :conditions => Project.visible_by(logged_in_user),
53 :conditions => Project.visible_by(logged_in_user),
53 :include => :parent
54 :include => :parent
54 @project_tree = projects.group_by {|p| p.parent || p}
55 @project_tree = projects.group_by {|p| p.parent || p}
55 @project_tree.each_key {|p| @project_tree[p] -= [p]}
56 @project_tree.each_key {|p| @project_tree[p] -= [p]}
56 end
57 end
57
58
58 # Add a new project
59 # Add a new project
59 def add
60 def add
60 @custom_fields = IssueCustomField.find(:all)
61 @custom_fields = IssueCustomField.find(:all)
61 @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
62 @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
62 @project = Project.new(params[:project])
63 @project = Project.new(params[:project])
63 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
64 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
64 if request.get?
65 if request.get?
65 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
66 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
66 else
67 else
67 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
68 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
68 @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)) }
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)) }
69 @project.custom_values = @custom_values
70 @project.custom_values = @custom_values
70 if @project.save
71 if @project.save
71 @project.enabled_module_names = params[:enabled_modules]
72 @project.enabled_module_names = params[:enabled_modules]
72 flash[:notice] = l(:notice_successful_create)
73 flash[:notice] = l(:notice_successful_create)
73 redirect_to :controller => 'admin', :action => 'projects'
74 redirect_to :controller => 'admin', :action => 'projects'
74 end
75 end
75 end
76 end
76 end
77 end
77
78
78 # Show @project
79 # Show @project
79 def show
80 def show
80 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
81 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
81 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
82 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
82 @subprojects = @project.active_children
83 @subprojects = @project.active_children
83 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
84 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
84 @trackers = Tracker.find(:all, :order => 'position')
85 @trackers = Tracker.find(:all, :order => 'position')
85 @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])
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])
86 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
87 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
87 @total_hours = @project.time_entries.sum(:hours)
88 @total_hours = @project.time_entries.sum(:hours)
88 @key = User.current.rss_key
89 @key = User.current.rss_key
89 end
90 end
90
91
91 def settings
92 def settings
92 @root_projects = Project::find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id])
93 @root_projects = Project::find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id])
93 @custom_fields = IssueCustomField.find(:all)
94 @custom_fields = IssueCustomField.find(:all)
94 @issue_category ||= IssueCategory.new
95 @issue_category ||= IssueCategory.new
95 @member ||= @project.members.new
96 @member ||= @project.members.new
96 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
97 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
97 @repository ||= @project.repository
98 @repository ||= @project.repository
98 @wiki ||= @project.wiki
99 @wiki ||= @project.wiki
99 end
100 end
100
101
101 # Edit @project
102 # Edit @project
102 def edit
103 def edit
103 if request.post?
104 if request.post?
104 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
105 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
105 if params[:custom_fields]
106 if params[:custom_fields]
106 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
107 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
107 @project.custom_values = @custom_values
108 @project.custom_values = @custom_values
108 end
109 end
109 @project.attributes = params[:project]
110 @project.attributes = params[:project]
110 if @project.save
111 if @project.save
111 flash[:notice] = l(:notice_successful_update)
112 flash[:notice] = l(:notice_successful_update)
112 redirect_to :action => 'settings', :id => @project
113 redirect_to :action => 'settings', :id => @project
113 else
114 else
114 settings
115 settings
115 render :action => 'settings'
116 render :action => 'settings'
116 end
117 end
117 end
118 end
118 end
119 end
119
120
120 def modules
121 def modules
121 @project.enabled_module_names = params[:enabled_modules]
122 @project.enabled_module_names = params[:enabled_modules]
122 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
123 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
123 end
124 end
124
125
125 def archive
126 def archive
126 @project.archive if request.post? && @project.active?
127 @project.archive if request.post? && @project.active?
127 redirect_to :controller => 'admin', :action => 'projects'
128 redirect_to :controller => 'admin', :action => 'projects'
128 end
129 end
129
130
130 def unarchive
131 def unarchive
131 @project.unarchive if request.post? && !@project.active?
132 @project.unarchive if request.post? && !@project.active?
132 redirect_to :controller => 'admin', :action => 'projects'
133 redirect_to :controller => 'admin', :action => 'projects'
133 end
134 end
134
135
135 # Delete @project
136 # Delete @project
136 def destroy
137 def destroy
137 @project_to_destroy = @project
138 @project_to_destroy = @project
138 if request.post? and params[:confirm]
139 if request.post? and params[:confirm]
139 @project_to_destroy.destroy
140 @project_to_destroy.destroy
140 redirect_to :controller => 'admin', :action => 'projects'
141 redirect_to :controller => 'admin', :action => 'projects'
141 end
142 end
142 # hide project in layout
143 # hide project in layout
143 @project = nil
144 @project = nil
144 end
145 end
145
146
146 # Add a new issue category to @project
147 # Add a new issue category to @project
147 def add_issue_category
148 def add_issue_category
148 @category = @project.issue_categories.build(params[:category])
149 @category = @project.issue_categories.build(params[:category])
149 if request.post? and @category.save
150 if request.post? and @category.save
150 respond_to do |format|
151 respond_to do |format|
151 format.html do
152 format.html do
152 flash[:notice] = l(:notice_successful_create)
153 flash[:notice] = l(:notice_successful_create)
153 redirect_to :action => 'settings', :tab => 'categories', :id => @project
154 redirect_to :action => 'settings', :tab => 'categories', :id => @project
154 end
155 end
155 format.js do
156 format.js do
156 # IE doesn't support the replace_html rjs method for select box options
157 # IE doesn't support the replace_html rjs method for select box options
157 render(:update) {|page| page.replace "issue_category_id",
158 render(:update) {|page| page.replace "issue_category_id",
158 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]')
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]')
159 }
160 }
160 end
161 end
161 end
162 end
162 end
163 end
163 end
164 end
164
165
165 # Add a new version to @project
166 # Add a new version to @project
166 def add_version
167 def add_version
167 @version = @project.versions.build(params[:version])
168 @version = @project.versions.build(params[:version])
168 if request.post? and @version.save
169 if request.post? and @version.save
169 flash[:notice] = l(:notice_successful_create)
170 flash[:notice] = l(:notice_successful_create)
170 redirect_to :action => 'settings', :tab => 'versions', :id => @project
171 redirect_to :action => 'settings', :tab => 'versions', :id => @project
171 end
172 end
172 end
173 end
173
174
174 # Add a new document to @project
175 # Add a new document to @project
175 def add_document
176 def add_document
176 @categories = Enumeration::get_values('DCAT')
177 @categories = Enumeration::get_values('DCAT')
177 @document = @project.documents.build(params[:document])
178 @document = @project.documents.build(params[:document])
178 if request.post? and @document.save
179 if request.post? and @document.save
179 # Save the attachments
180 # Save the attachments
180 params[:attachments].each { |a|
181 params[:attachments].each { |a|
181 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
182 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
182 } if params[:attachments] and params[:attachments].is_a? Array
183 } if params[:attachments] and params[:attachments].is_a? Array
183 flash[:notice] = l(:notice_successful_create)
184 flash[:notice] = l(:notice_successful_create)
184 Mailer.deliver_document_add(@document) if Setting.notified_events.include?('document_added')
185 Mailer.deliver_document_add(@document) if Setting.notified_events.include?('document_added')
185 redirect_to :action => 'list_documents', :id => @project
186 redirect_to :action => 'list_documents', :id => @project
186 end
187 end
187 end
188 end
188
189
189 # Show documents list of @project
190 # Show documents list of @project
190 def list_documents
191 def list_documents
191 @documents = @project.documents.find :all, :include => :category
192 @documents = @project.documents.find :all, :include => :category
192 end
193 end
193
194
194 # Add a new issue to @project
195 # Add a new issue to @project
195 def add_issue
196 def add_issue
196 @tracker = Tracker.find(params[:tracker_id])
197 @tracker = Tracker.find(params[:tracker_id])
197 @priorities = Enumeration::get_values('IPRI')
198 @priorities = Enumeration::get_values('IPRI')
198
199
199 default_status = IssueStatus.default
200 default_status = IssueStatus.default
200 unless default_status
201 unless default_status
201 flash.now[:error] = 'No default issue status defined. Please check your configuration.'
202 flash.now[:error] = 'No default issue status defined. Please check your configuration.'
202 render :nothing => true, :layout => true
203 render :nothing => true, :layout => true
203 return
204 return
204 end
205 end
205 @issue = Issue.new(:project => @project, :tracker => @tracker)
206 @issue = Issue.new(:project => @project, :tracker => @tracker)
206 @issue.status = default_status
207 @issue.status = default_status
207 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
208 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
208 if request.get?
209 if request.get?
209 @issue.start_date = Date.today
210 @issue.start_date = Date.today
210 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
211 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
211 else
212 else
212 @issue.attributes = params[:issue]
213 @issue.attributes = params[:issue]
213
214
214 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
215 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
215 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
216 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
216
217
217 @issue.author_id = self.logged_in_user.id if self.logged_in_user
218 @issue.author_id = self.logged_in_user.id if self.logged_in_user
218 # Multiple file upload
219 # Multiple file upload
219 @attachments = []
220 @attachments = []
220 params[:attachments].each { |a|
221 params[:attachments].each { |a|
221 @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
222 @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
222 } if params[:attachments] and params[:attachments].is_a? Array
223 } if params[:attachments] and params[:attachments].is_a? Array
223 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
224 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
224 @issue.custom_values = @custom_values
225 @issue.custom_values = @custom_values
225 if @issue.save
226 if @issue.save
226 @attachments.each(&:save)
227 @attachments.each(&:save)
227 flash[:notice] = l(:notice_successful_create)
228 flash[:notice] = l(:notice_successful_create)
228 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
229 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
229 redirect_to :action => 'list_issues', :id => @project
230 redirect_to :action => 'list_issues', :id => @project
230 end
231 end
231 end
232 end
232 end
233 end
233
234
234 # Show filtered/sorted issues list of @project
235 # Show filtered/sorted issues list of @project
235 def list_issues
236 def list_issues
236 sort_init "#{Issue.table_name}.id", "desc"
237 sort_init "#{Issue.table_name}.id", "desc"
237 sort_update
238 sort_update
238
239
239 retrieve_query
240 retrieve_query
240
241
241 @results_per_page_options = [ 15, 25, 50, 100 ]
242 @results_per_page_options = [ 15, 25, 50, 100 ]
242 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
243 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
243 @results_per_page = params[:per_page].to_i
244 @results_per_page = params[:per_page].to_i
244 session[:results_per_page] = @results_per_page
245 session[:results_per_page] = @results_per_page
245 else
246 else
246 @results_per_page = session[:results_per_page] || 25
247 @results_per_page = session[:results_per_page] || 25
247 end
248 end
248
249
249 if @query.valid?
250 if @query.valid?
250 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
251 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
251 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
252 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
252 @issues = Issue.find :all, :order => sort_clause,
253 @issues = Issue.find :all, :order => sort_clause,
253 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
254 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
254 :conditions => @query.statement,
255 :conditions => @query.statement,
255 :limit => @issue_pages.items_per_page,
256 :limit => @issue_pages.items_per_page,
256 :offset => @issue_pages.current.offset
257 :offset => @issue_pages.current.offset
257 end
258 end
258
259
259 render :layout => false if request.xhr?
260 render :layout => false if request.xhr?
260 end
261 end
261
262
262 # Export filtered/sorted issues list to CSV
263 # Export filtered/sorted issues list to CSV
263 def export_issues_csv
264 def export_issues_csv
264 sort_init "#{Issue.table_name}.id", "desc"
265 sort_init "#{Issue.table_name}.id", "desc"
265 sort_update
266 sort_update
266
267
267 retrieve_query
268 retrieve_query
268 render :action => 'list_issues' and return unless @query.valid?
269 render :action => 'list_issues' and return unless @query.valid?
269
270
270 @issues = Issue.find :all, :order => sort_clause,
271 @issues = Issue.find :all, :order => sort_clause,
271 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
272 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
272 :conditions => @query.statement,
273 :conditions => @query.statement,
273 :limit => Setting.issues_export_limit.to_i
274 :limit => Setting.issues_export_limit.to_i
274
275
275 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
276 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
276 export = StringIO.new
277 export = StringIO.new
277 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
278 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
278 # csv header fields
279 # csv header fields
279 headers = [ "#", l(:field_status),
280 headers = [ "#", l(:field_status),
280 l(:field_project),
281 l(:field_project),
281 l(:field_tracker),
282 l(:field_tracker),
282 l(:field_priority),
283 l(:field_priority),
283 l(:field_subject),
284 l(:field_subject),
284 l(:field_assigned_to),
285 l(:field_assigned_to),
285 l(:field_author),
286 l(:field_author),
286 l(:field_start_date),
287 l(:field_start_date),
287 l(:field_due_date),
288 l(:field_due_date),
288 l(:field_done_ratio),
289 l(:field_done_ratio),
289 l(:field_created_on),
290 l(:field_created_on),
290 l(:field_updated_on)
291 l(:field_updated_on)
291 ]
292 ]
292 for custom_field in @project.all_custom_fields
293 for custom_field in @project.all_custom_fields
293 headers << custom_field.name
294 headers << custom_field.name
294 end
295 end
295 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
296 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
296 # csv lines
297 # csv lines
297 @issues.each do |issue|
298 @issues.each do |issue|
298 fields = [issue.id, issue.status.name,
299 fields = [issue.id, issue.status.name,
299 issue.project.name,
300 issue.project.name,
300 issue.tracker.name,
301 issue.tracker.name,
301 issue.priority.name,
302 issue.priority.name,
302 issue.subject,
303 issue.subject,
303 (issue.assigned_to ? issue.assigned_to.name : ""),
304 (issue.assigned_to ? issue.assigned_to.name : ""),
304 issue.author.name,
305 issue.author.name,
305 issue.start_date ? l_date(issue.start_date) : nil,
306 issue.start_date ? l_date(issue.start_date) : nil,
306 issue.due_date ? l_date(issue.due_date) : nil,
307 issue.due_date ? l_date(issue.due_date) : nil,
307 issue.done_ratio,
308 issue.done_ratio,
308 l_datetime(issue.created_on),
309 l_datetime(issue.created_on),
309 l_datetime(issue.updated_on)
310 l_datetime(issue.updated_on)
310 ]
311 ]
311 for custom_field in @project.all_custom_fields
312 for custom_field in @project.all_custom_fields
312 fields << (show_value issue.custom_value_for(custom_field))
313 fields << (show_value issue.custom_value_for(custom_field))
313 end
314 end
314 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
315 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
315 end
316 end
316 end
317 end
317 export.rewind
318 export.rewind
318 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
319 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
319 end
320 end
320
321
321 # Export filtered/sorted issues to PDF
322 # Export filtered/sorted issues to PDF
322 def export_issues_pdf
323 def export_issues_pdf
323 sort_init "#{Issue.table_name}.id", "desc"
324 sort_init "#{Issue.table_name}.id", "desc"
324 sort_update
325 sort_update
325
326
326 retrieve_query
327 retrieve_query
327 render :action => 'list_issues' and return unless @query.valid?
328 render :action => 'list_issues' and return unless @query.valid?
328
329
329 @issues = Issue.find :all, :order => sort_clause,
330 @issues = Issue.find :all, :order => sort_clause,
330 :include => [ :author, :status, :tracker, :priority, :project ],
331 :include => [ :author, :status, :tracker, :priority, :project ],
331 :conditions => @query.statement,
332 :conditions => @query.statement,
332 :limit => Setting.issues_export_limit.to_i
333 :limit => Setting.issues_export_limit.to_i
333
334
334 @options_for_rfpdf ||= {}
335 @options_for_rfpdf ||= {}
335 @options_for_rfpdf[:file_name] = "export.pdf"
336 @options_for_rfpdf[:file_name] = "export.pdf"
336 render :layout => false
337 render :layout => false
337 end
338 end
338
339
339 def move_issues
340 def move_issues
340 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
341 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
341 redirect_to :action => 'list_issues', :id => @project and return unless @issues
342 redirect_to :action => 'list_issues', :id => @project and return unless @issues
342 @projects = []
343 @projects = []
343 # find projects to which the user is allowed to move the issue
344 # find projects to which the user is allowed to move the issue
344 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:controller => 'projects', :action => 'move_issues')}
345 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:controller => 'projects', :action => 'move_issues')}
345 # issue can be moved to any tracker
346 # issue can be moved to any tracker
346 @trackers = Tracker.find(:all)
347 @trackers = Tracker.find(:all)
347 if request.post? and params[:new_project_id] and params[:new_tracker_id]
348 if request.post? and params[:new_project_id] and params[:new_tracker_id]
348 new_project = Project.find_by_id(params[:new_project_id])
349 new_project = Project.find_by_id(params[:new_project_id])
349 new_tracker = Tracker.find_by_id(params[:new_tracker_id])
350 new_tracker = Tracker.find_by_id(params[:new_tracker_id])
350 @issues.each do |i|
351 @issues.each do |i|
351 if new_project && i.project_id != new_project.id
352 if new_project && i.project_id != new_project.id
352 # issue is moved to another project
353 # issue is moved to another project
353 i.category = nil
354 i.category = nil
354 i.fixed_version = nil
355 i.fixed_version = nil
355 # delete issue relations
356 # delete issue relations
356 i.relations_from.clear
357 i.relations_from.clear
357 i.relations_to.clear
358 i.relations_to.clear
358 i.project = new_project
359 i.project = new_project
359 end
360 end
360 if new_tracker
361 if new_tracker
361 i.tracker = new_tracker
362 i.tracker = new_tracker
362 end
363 end
363 i.save
364 i.save
364 end
365 end
365 flash[:notice] = l(:notice_successful_update)
366 flash[:notice] = l(:notice_successful_update)
366 redirect_to :action => 'list_issues', :id => @project
367 redirect_to :action => 'list_issues', :id => @project
367 end
368 end
368 end
369 end
369
370
370 # Add a news to @project
371 # Add a news to @project
371 def add_news
372 def add_news
372 @news = News.new(:project => @project)
373 @news = News.new(:project => @project)
373 if request.post?
374 if request.post?
374 @news.attributes = params[:news]
375 @news.attributes = params[:news]
375 @news.author_id = self.logged_in_user.id if self.logged_in_user
376 @news.author_id = self.logged_in_user.id if self.logged_in_user
376 if @news.save
377 if @news.save
377 flash[:notice] = l(:notice_successful_create)
378 flash[:notice] = l(:notice_successful_create)
378 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
379 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
379 redirect_to :action => 'list_news', :id => @project
380 redirect_to :action => 'list_news', :id => @project
380 end
381 end
381 end
382 end
382 end
383 end
383
384
384 # Show news list of @project
385 # Show news list of @project
385 def list_news
386 def list_news
386 @news_pages, @newss = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
387 @news_pages, @newss = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
387
388
388 respond_to do |format|
389 respond_to do |format|
389 format.html { render :layout => false if request.xhr? }
390 format.html { render :layout => false if request.xhr? }
390 format.atom { render_feed(@newss, :title => "#{@project.name}: #{l(:label_news_plural)}") }
391 format.atom { render_feed(@newss, :title => "#{@project.name}: #{l(:label_news_plural)}") }
391 end
392 end
392 end
393 end
393
394
394 def add_file
395 def add_file
395 if request.post?
396 if request.post?
396 @version = @project.versions.find_by_id(params[:version_id])
397 @version = @project.versions.find_by_id(params[:version_id])
397 # Save the attachments
398 # Save the attachments
398 @attachments = []
399 @attachments = []
399 params[:attachments].each { |file|
400 params[:attachments].each { |file|
400 next unless file.size > 0
401 next unless file.size > 0
401 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
402 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
402 @attachments << a unless a.new_record?
403 @attachments << a unless a.new_record?
403 } if params[:attachments] and params[:attachments].is_a? Array
404 } if params[:attachments] and params[:attachments].is_a? Array
404 Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? && Setting.notified_events.include?('file_added')
405 Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? && Setting.notified_events.include?('file_added')
405 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
406 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
406 end
407 end
407 @versions = @project.versions.sort
408 @versions = @project.versions.sort
408 end
409 end
409
410
410 def list_files
411 def list_files
411 @versions = @project.versions.sort
412 @versions = @project.versions.sort
412 end
413 end
413
414
414 # Show changelog for @project
415 # Show changelog for @project
415 def changelog
416 def changelog
416 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
417 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
417 retrieve_selected_tracker_ids(@trackers)
418 retrieve_selected_tracker_ids(@trackers)
418 @versions = @project.versions.sort
419 @versions = @project.versions.sort
419 end
420 end
420
421
421 def roadmap
422 def roadmap
422 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
423 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
423 retrieve_selected_tracker_ids(@trackers)
424 retrieve_selected_tracker_ids(@trackers)
424 @versions = @project.versions.sort
425 @versions = @project.versions.sort
425 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
426 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
426 end
427 end
427
428
428 def activity
429 def activity
429 if params[:year] and params[:year].to_i > 1900
430 if params[:year] and params[:year].to_i > 1900
430 @year = params[:year].to_i
431 @year = params[:year].to_i
431 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
432 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
432 @month = params[:month].to_i
433 @month = params[:month].to_i
433 end
434 end
434 end
435 end
435 @year ||= Date.today.year
436 @year ||= Date.today.year
436 @month ||= Date.today.month
437 @month ||= Date.today.month
437
438
438 case params[:format]
439 case params[:format]
439 when 'atom'
440 when 'atom'
440 # 30 last days
441 # 30 last days
441 @date_from = Date.today - 30
442 @date_from = Date.today - 30
442 @date_to = Date.today + 1
443 @date_to = Date.today + 1
443 else
444 else
444 # current month
445 # current month
445 @date_from = Date.civil(@year, @month, 1)
446 @date_from = Date.civil(@year, @month, 1)
446 @date_to = @date_from >> 1
447 @date_to = @date_from >> 1
447 end
448 end
448
449
449 @event_types = %w(issues news files documents wiki_pages changesets)
450 @event_types = %w(issues news files documents wiki_pages changesets)
450 @event_types.delete('wiki_pages') unless @project.wiki
451 @event_types.delete('wiki_pages') unless @project.wiki
451 @event_types.delete('changesets') unless @project.repository
452 @event_types.delete('changesets') unless @project.repository
452 # only show what the user is allowed to view
453 # only show what the user is allowed to view
453 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
454 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
454
455
455 @scope = @event_types.select {|t| params["show_#{t}"]}
456 @scope = @event_types.select {|t| params["show_#{t}"]}
456 # default events if none is specified in parameters
457 # default events if none is specified in parameters
457 @scope = (@event_types - %w(wiki_pages))if @scope.empty?
458 @scope = (@event_types - %w(wiki_pages))if @scope.empty?
458
459
459 @events = []
460 @events = []
460
461
461 if @scope.include?('issues')
462 if @scope.include?('issues')
462 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
463 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
463 end
464 end
464
465
465 if @scope.include?('news')
466 if @scope.include?('news')
466 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
467 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
467 end
468 end
468
469
469 if @scope.include?('files')
470 if @scope.include?('files')
470 @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 )
471 @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 )
471 end
472 end
472
473
473 if @scope.include?('documents')
474 if @scope.include?('documents')
474 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
475 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
475 @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 )
476 @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 )
476 end
477 end
477
478
478 if @scope.include?('wiki_pages')
479 if @scope.include?('wiki_pages')
479 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
480 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
480 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
481 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
481 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
482 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
482 "#{WikiContent.versioned_table_name}.id"
483 "#{WikiContent.versioned_table_name}.id"
483 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
484 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
484 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
485 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
485 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
486 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
486 @project.id, @date_from, @date_to]
487 @project.id, @date_from, @date_to]
487
488
488 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
489 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
489 end
490 end
490
491
491 if @scope.include?('changesets')
492 if @scope.include?('changesets')
492 @events += @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
493 @events += @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
493 end
494 end
494
495
495 @events_by_day = @events.group_by(&:event_date)
496 @events_by_day = @events.group_by(&:event_date)
496
497
497 respond_to do |format|
498 respond_to do |format|
498 format.html { render :layout => false if request.xhr? }
499 format.html { render :layout => false if request.xhr? }
499 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
500 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
500 end
501 end
501 end
502 end
502
503
503 def calendar
504 def calendar
504 @trackers = Tracker.find(:all, :order => 'position')
505 @trackers = Tracker.find(:all, :order => 'position')
505 retrieve_selected_tracker_ids(@trackers)
506 retrieve_selected_tracker_ids(@trackers)
506
507
507 if params[:year] and params[:year].to_i > 1900
508 if params[:year] and params[:year].to_i > 1900
508 @year = params[:year].to_i
509 @year = params[:year].to_i
509 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
510 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
510 @month = params[:month].to_i
511 @month = params[:month].to_i
511 end
512 end
512 end
513 end
513 @year ||= Date.today.year
514 @year ||= Date.today.year
514 @month ||= Date.today.month
515 @month ||= Date.today.month
515
516
516 @date_from = Date.civil(@year, @month, 1)
517 @date_from = Date.civil(@year, @month, 1)
517 @date_to = (@date_from >> 1)-1
518 @date_to = (@date_from >> 1)-1
518 # start on monday
519 # start on monday
519 @date_from = @date_from - (@date_from.cwday-1)
520 @date_from = @date_from - (@date_from.cwday-1)
520 # finish on sunday
521 # finish on sunday
521 @date_to = @date_to + (7-@date_to.cwday)
522 @date_to = @date_to + (7-@date_to.cwday)
522
523
523 @events = []
524 @events = []
524 @project.issues_with_subprojects(params[:with_subprojects]) do
525 @project.issues_with_subprojects(params[:with_subprojects]) do
525 @events += Issue.find(:all,
526 @events += Issue.find(:all,
526 :include => [:tracker, :status, :assigned_to, :priority, :project],
527 :include => [:tracker, :status, :assigned_to, :priority, :project],
527 :conditions => ["((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)) and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')})", @date_from, @date_to, @date_from, @date_to]
528 :conditions => ["((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)) and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')})", @date_from, @date_to, @date_from, @date_to]
528 ) unless @selected_tracker_ids.empty?
529 ) unless @selected_tracker_ids.empty?
529 end
530 end
530 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
531 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
531
532
532 @ending_events_by_days = @events.group_by {|event| event.due_date}
533 @ending_events_by_days = @events.group_by {|event| event.due_date}
533 @starting_events_by_days = @events.group_by {|event| event.start_date}
534 @starting_events_by_days = @events.group_by {|event| event.start_date}
534
535
535 render :layout => false if request.xhr?
536 render :layout => false if request.xhr?
536 end
537 end
537
538
538 def gantt
539 def gantt
539 @trackers = Tracker.find(:all, :order => 'position')
540 @trackers = Tracker.find(:all, :order => 'position')
540 retrieve_selected_tracker_ids(@trackers)
541 retrieve_selected_tracker_ids(@trackers)
541
542
542 if params[:year] and params[:year].to_i >0
543 if params[:year] and params[:year].to_i >0
543 @year_from = params[:year].to_i
544 @year_from = params[:year].to_i
544 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
545 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
545 @month_from = params[:month].to_i
546 @month_from = params[:month].to_i
546 else
547 else
547 @month_from = 1
548 @month_from = 1
548 end
549 end
549 else
550 else
550 @month_from ||= (Date.today << 1).month
551 @month_from ||= (Date.today << 1).month
551 @year_from ||= (Date.today << 1).year
552 @year_from ||= (Date.today << 1).year
552 end
553 end
553
554
554 @zoom = (params[:zoom].to_i > 0 and params[:zoom].to_i < 5) ? params[:zoom].to_i : 2
555 @zoom = (params[:zoom].to_i > 0 and params[:zoom].to_i < 5) ? params[:zoom].to_i : 2
555 @months = (params[:months].to_i > 0 and params[:months].to_i < 25) ? params[:months].to_i : 6
556 @months = (params[:months].to_i > 0 and params[:months].to_i < 25) ? params[:months].to_i : 6
556
557
557 @date_from = Date.civil(@year_from, @month_from, 1)
558 @date_from = Date.civil(@year_from, @month_from, 1)
558 @date_to = (@date_from >> @months) - 1
559 @date_to = (@date_from >> @months) - 1
559
560
560 @events = []
561 @events = []
561 @project.issues_with_subprojects(params[:with_subprojects]) do
562 @project.issues_with_subprojects(params[:with_subprojects]) do
562 @events += Issue.find(:all,
563 @events += Issue.find(:all,
563 :order => "start_date, due_date",
564 :order => "start_date, due_date",
564 :include => [:tracker, :status, :assigned_to, :priority, :project],
565 :include => [:tracker, :status, :assigned_to, :priority, :project],
565 :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]
566 :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]
566 ) unless @selected_tracker_ids.empty?
567 ) unless @selected_tracker_ids.empty?
567 end
568 end
568 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
569 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
569 @events.sort! {|x,y| x.start_date <=> y.start_date }
570 @events.sort! {|x,y| x.start_date <=> y.start_date }
570
571
571 if params[:format]=='pdf'
572 if params[:format]=='pdf'
572 @options_for_rfpdf ||= {}
573 @options_for_rfpdf ||= {}
573 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
574 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
574 render :template => "projects/gantt.rfpdf", :layout => false
575 render :template => "projects/gantt.rfpdf", :layout => false
575 elsif params[:format]=='png' && respond_to?('gantt_image')
576 elsif params[:format]=='png' && respond_to?('gantt_image')
576 image = gantt_image(@events, @date_from, @months, @zoom)
577 image = gantt_image(@events, @date_from, @months, @zoom)
577 image.format = 'PNG'
578 image.format = 'PNG'
578 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
579 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
579 else
580 else
580 render :template => "projects/gantt.rhtml"
581 render :template => "projects/gantt.rhtml"
581 end
582 end
582 end
583 end
583
584
584 private
585 private
585 # Find project of id params[:id]
586 # Find project of id params[:id]
586 # if not found, redirect to project list
587 # if not found, redirect to project list
587 # Used as a before_filter
588 # Used as a before_filter
588 def find_project
589 def find_project
589 @project = Project.find(params[:id])
590 @project = Project.find(params[:id])
590 rescue ActiveRecord::RecordNotFound
591 rescue ActiveRecord::RecordNotFound
591 render_404
592 render_404
592 end
593 end
593
594
594 def retrieve_selected_tracker_ids(selectable_trackers)
595 def retrieve_selected_tracker_ids(selectable_trackers)
595 if ids = params[:tracker_ids]
596 if ids = params[:tracker_ids]
596 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
597 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
597 else
598 else
598 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
599 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
599 end
600 end
600 end
601 end
601
602
602 # Retrieve query from session or build a new query
603 # Retrieve query from session or build a new query
603 def retrieve_query
604 def retrieve_query
604 if params[:query_id]
605 if params[:query_id]
605 @query = @project.queries.find(params[:query_id])
606 @query = @project.queries.find(params[:query_id])
606 @query.executed_by = logged_in_user
607 @query.executed_by = logged_in_user
607 session[:query] = @query
608 session[:query] = @query
608 else
609 else
609 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
610 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
610 # Give it a name, required to be valid
611 # Give it a name, required to be valid
611 @query = Query.new(:name => "_", :executed_by => logged_in_user)
612 @query = Query.new(:name => "_", :executed_by => logged_in_user)
612 @query.project = @project
613 @query.project = @project
613 if params[:fields] and params[:fields].is_a? Array
614 if params[:fields] and params[:fields].is_a? Array
614 params[:fields].each do |field|
615 params[:fields].each do |field|
615 @query.add_filter(field, params[:operators][field], params[:values][field])
616 @query.add_filter(field, params[:operators][field], params[:values][field])
616 end
617 end
617 else
618 else
618 @query.available_filters.keys.each do |field|
619 @query.available_filters.keys.each do |field|
619 @query.add_short_filter(field, params[field]) if params[field]
620 @query.add_short_filter(field, params[field]) if params[field]
620 end
621 end
621 end
622 end
622 session[:query] = @query
623 session[:query] = @query
623 else
624 else
624 @query = session[:query]
625 @query = session[:query]
625 end
626 end
626 end
627 end
627 end
628 end
628 end
629 end
@@ -1,171 +1,172
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 TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
21
21
22 helper :sort
22 helper :sort
23 include SortHelper
23 include SortHelper
24 helper :issues
24
25
25 def report
26 def report
26 @available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
27 @available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
27 :values => @project.versions,
28 :values => @project.versions,
28 :label => :label_version},
29 :label => :label_version},
29 'category' => {:sql => "#{Issue.table_name}.category_id",
30 'category' => {:sql => "#{Issue.table_name}.category_id",
30 :values => @project.issue_categories,
31 :values => @project.issue_categories,
31 :label => :field_category},
32 :label => :field_category},
32 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
33 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
33 :values => @project.users,
34 :values => @project.users,
34 :label => :label_member},
35 :label => :label_member},
35 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
36 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
36 :values => Tracker.find(:all),
37 :values => Tracker.find(:all),
37 :label => :label_tracker},
38 :label => :label_tracker},
38 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
39 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
39 :values => Enumeration::get_values('ACTI'),
40 :values => Enumeration::get_values('ACTI'),
40 :label => :label_activity}
41 :label => :label_activity}
41 }
42 }
42
43
43 @criterias = params[:criterias] || []
44 @criterias = params[:criterias] || []
44 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
45 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
45 @criterias.uniq!
46 @criterias.uniq!
46
47
47 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
48 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
48
49
49 if params[:date_from]
50 if params[:date_from]
50 begin; @date_from = params[:date_from].to_date; rescue; end
51 begin; @date_from = params[:date_from].to_date; rescue; end
51 end
52 end
52 if params[:date_to]
53 if params[:date_to]
53 begin; @date_to = params[:date_to].to_date; rescue; end
54 begin; @date_to = params[:date_to].to_date; rescue; end
54 end
55 end
55 @date_from ||= Date.civil(Date.today.year, 1, 1)
56 @date_from ||= Date.civil(Date.today.year, 1, 1)
56 @date_to ||= Date.civil(Date.today.year, Date.today.month+1, 1) - 1
57 @date_to ||= Date.civil(Date.today.year, Date.today.month+1, 1) - 1
57
58
58 unless @criterias.empty?
59 unless @criterias.empty?
59 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
60 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
60 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
61 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
61
62
62 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
63 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
63 sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
64 sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
64 sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
65 sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
65 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
66 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
66 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
67 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
67
68
68 @hours = ActiveRecord::Base.connection.select_all(sql)
69 @hours = ActiveRecord::Base.connection.select_all(sql)
69
70
70 @hours.each do |row|
71 @hours.each do |row|
71 case @columns
72 case @columns
72 when 'year'
73 when 'year'
73 row['year'] = row['tyear']
74 row['year'] = row['tyear']
74 when 'month'
75 when 'month'
75 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
76 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
76 when 'week'
77 when 'week'
77 row['week'] = "#{row['tyear']}-#{row['tweek']}"
78 row['week'] = "#{row['tyear']}-#{row['tweek']}"
78 end
79 end
79 end
80 end
80 end
81 end
81
82
82 @periods = []
83 @periods = []
83 date_from = @date_from
84 date_from = @date_from
84 # 100 columns max
85 # 100 columns max
85 while date_from < @date_to && @periods.length < 100
86 while date_from < @date_to && @periods.length < 100
86 case @columns
87 case @columns
87 when 'year'
88 when 'year'
88 @periods << "#{date_from.year}"
89 @periods << "#{date_from.year}"
89 date_from = date_from >> 12
90 date_from = date_from >> 12
90 when 'month'
91 when 'month'
91 @periods << "#{date_from.year}-#{date_from.month}"
92 @periods << "#{date_from.year}-#{date_from.month}"
92 date_from = date_from >> 1
93 date_from = date_from >> 1
93 when 'week'
94 when 'week'
94 @periods << "#{date_from.year}-#{date_from.cweek}"
95 @periods << "#{date_from.year}-#{date_from.cweek}"
95 date_from = date_from + 7
96 date_from = date_from + 7
96 end
97 end
97 end
98 end
98
99
99 render :layout => false if request.xhr?
100 render :layout => false if request.xhr?
100 end
101 end
101
102
102 def details
103 def details
103 sort_init 'spent_on', 'desc'
104 sort_init 'spent_on', 'desc'
104 sort_update
105 sort_update
105
106
106 @entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
107 @entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
107
108
108 @total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
109 @total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
109 @owner_id = logged_in_user ? logged_in_user.id : 0
110 @owner_id = logged_in_user ? logged_in_user.id : 0
110
111
111 send_csv and return if 'csv' == params[:export]
112 send_csv and return if 'csv' == params[:export]
112 render :action => 'details', :layout => false if request.xhr?
113 render :action => 'details', :layout => false if request.xhr?
113 end
114 end
114
115
115 def edit
116 def edit
116 render_404 and return if @time_entry && @time_entry.user != logged_in_user
117 render_404 and return if @time_entry && @time_entry.user != logged_in_user
117 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
118 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
118 @time_entry.attributes = params[:time_entry]
119 @time_entry.attributes = params[:time_entry]
119 if request.post? and @time_entry.save
120 if request.post? and @time_entry.save
120 flash[:notice] = l(:notice_successful_update)
121 flash[:notice] = l(:notice_successful_update)
121 redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
122 redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
122 return
123 return
123 end
124 end
124 @activities = Enumeration::get_values('ACTI')
125 @activities = Enumeration::get_values('ACTI')
125 end
126 end
126
127
127 private
128 private
128 def find_project
129 def find_project
129 if params[:id]
130 if params[:id]
130 @time_entry = TimeEntry.find(params[:id])
131 @time_entry = TimeEntry.find(params[:id])
131 @project = @time_entry.project
132 @project = @time_entry.project
132 elsif params[:issue_id]
133 elsif params[:issue_id]
133 @issue = Issue.find(params[:issue_id])
134 @issue = Issue.find(params[:issue_id])
134 @project = @issue.project
135 @project = @issue.project
135 elsif params[:project_id]
136 elsif params[:project_id]
136 @project = Project.find(params[:project_id])
137 @project = Project.find(params[:project_id])
137 else
138 else
138 render_404
139 render_404
139 return false
140 return false
140 end
141 end
141 end
142 end
142
143
143 def send_csv
144 def send_csv
144 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
145 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
145 export = StringIO.new
146 export = StringIO.new
146 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
147 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
147 # csv header fields
148 # csv header fields
148 headers = [l(:field_spent_on),
149 headers = [l(:field_spent_on),
149 l(:field_user),
150 l(:field_user),
150 l(:field_activity),
151 l(:field_activity),
151 l(:field_issue),
152 l(:field_issue),
152 l(:field_hours),
153 l(:field_hours),
153 l(:field_comments)
154 l(:field_comments)
154 ]
155 ]
155 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
156 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
156 # csv lines
157 # csv lines
157 @entries.each do |entry|
158 @entries.each do |entry|
158 fields = [l_date(entry.spent_on),
159 fields = [l_date(entry.spent_on),
159 entry.user.name,
160 entry.user.name,
160 entry.activity.name,
161 entry.activity.name,
161 (entry.issue ? entry.issue.id : nil),
162 (entry.issue ? entry.issue.id : nil),
162 entry.hours,
163 entry.hours,
163 entry.comments
164 entry.comments
164 ]
165 ]
165 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
166 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
166 end
167 end
167 end
168 end
168 export.rewind
169 export.rewind
169 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
170 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
170 end
171 end
171 end
172 end
@@ -1,343 +1,343
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 module ApplicationHelper
18 module ApplicationHelper
19
19
20 def current_role
20 def current_role
21 @current_role ||= User.current.role_for_project(@project)
21 @current_role ||= User.current.role_for_project(@project)
22 end
22 end
23
23
24 # Return true if user is authorized for controller/action, otherwise false
24 # Return true if user is authorized for controller/action, otherwise false
25 def authorize_for(controller, action)
25 def authorize_for(controller, action)
26 User.current.allowed_to?({:controller => controller, :action => action}, @project)
26 User.current.allowed_to?({:controller => controller, :action => action}, @project)
27 end
27 end
28
28
29 # Display a link if user is authorized
29 # Display a link if user is authorized
30 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
30 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
31 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
31 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
32 end
32 end
33
33
34 # Display a link to user's account page
34 # Display a link to user's account page
35 def link_to_user(user)
35 def link_to_user(user)
36 link_to user.name, :controller => 'account', :action => 'show', :id => user
36 link_to user.name, :controller => 'account', :action => 'show', :id => user
37 end
37 end
38
38
39 def link_to_issue(issue)
39 def link_to_issue(issue)
40 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
40 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
41 end
41 end
42
42
43 def toggle_link(name, id, options={})
43 def toggle_link(name, id, options={})
44 onclick = "Element.toggle('#{id}'); "
44 onclick = "Element.toggle('#{id}'); "
45 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
45 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
46 onclick << "return false;"
46 onclick << "return false;"
47 link_to(name, "#", :onclick => onclick)
47 link_to(name, "#", :onclick => onclick)
48 end
48 end
49
49
50 def show_and_goto_link(name, id, options={})
50 def show_and_goto_link(name, id, options={})
51 onclick = "Element.show('#{id}'); "
51 onclick = "Element.show('#{id}'); "
52 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
52 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
53 onclick << "location.href='##{id}-anchor'; "
53 onclick << "location.href='##{id}-anchor'; "
54 onclick << "return false;"
54 onclick << "return false;"
55 link_to(name, "#", options.merge(:onclick => onclick))
55 link_to(name, "#", options.merge(:onclick => onclick))
56 end
56 end
57
57
58 def image_to_function(name, function, html_options = {})
58 def image_to_function(name, function, html_options = {})
59 html_options.symbolize_keys!
59 html_options.symbolize_keys!
60 tag(:input, html_options.merge({
60 tag(:input, html_options.merge({
61 :type => "image", :src => image_path(name),
61 :type => "image", :src => image_path(name),
62 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
62 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
63 }))
63 }))
64 end
64 end
65
65
66 def prompt_to_remote(name, text, param, url, html_options = {})
66 def prompt_to_remote(name, text, param, url, html_options = {})
67 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
67 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
68 link_to name, {}, html_options
68 link_to name, {}, html_options
69 end
69 end
70
70
71 def format_date(date)
71 def format_date(date)
72 return nil unless date
72 return nil unless date
73 @date_format_setting ||= Setting.date_format.to_i
73 @date_format ||= (Setting.date_format.to_i == 0 ? l(:general_fmt_date) : date.strftime("%Y-%m-%d"))
74 @date_format_setting == 0 ? l_date(date) : date.strftime("%Y-%m-%d")
74 date.strftime(@date_format)
75 end
75 end
76
76
77 def format_time(time)
77 def format_time(time)
78 return nil unless time
78 return nil unless time
79 @date_format_setting ||= Setting.date_format.to_i
79 @date_format_setting ||= Setting.date_format.to_i
80 time = time.to_time if time.is_a?(String)
80 time = time.to_time if time.is_a?(String)
81 @date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time))
81 @date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time))
82 end
82 end
83
83
84 def authoring(created, author)
84 def authoring(created, author)
85 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
85 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
86 l(:label_added_time_by, author.name, time_tag)
86 l(:label_added_time_by, author.name, time_tag)
87 end
87 end
88
88
89 def day_name(day)
89 def day_name(day)
90 l(:general_day_names).split(',')[day-1]
90 l(:general_day_names).split(',')[day-1]
91 end
91 end
92
92
93 def month_name(month)
93 def month_name(month)
94 l(:actionview_datehelper_select_month_names).split(',')[month-1]
94 l(:actionview_datehelper_select_month_names).split(',')[month-1]
95 end
95 end
96
96
97 def pagination_links_full(paginator, options={}, html_options={})
97 def pagination_links_full(paginator, options={}, html_options={})
98 page_param = options.delete(:page_param) || :page
98 page_param = options.delete(:page_param) || :page
99
99
100 html = ''
100 html = ''
101 html << link_to_remote(('&#171; ' + l(:label_previous)),
101 html << link_to_remote(('&#171; ' + l(:label_previous)),
102 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
102 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
103 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
103 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
104
104
105 html << (pagination_links_each(paginator, options) do |n|
105 html << (pagination_links_each(paginator, options) do |n|
106 link_to_remote(n.to_s,
106 link_to_remote(n.to_s,
107 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
107 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
108 {:href => url_for(:params => options.merge(page_param => n))})
108 {:href => url_for(:params => options.merge(page_param => n))})
109 end || '')
109 end || '')
110
110
111 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
111 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
112 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
112 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
113 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
113 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
114 html
114 html
115 end
115 end
116
116
117 def set_html_title(text)
117 def set_html_title(text)
118 @html_header_title = text
118 @html_header_title = text
119 end
119 end
120
120
121 def html_title
121 def html_title
122 title = []
122 title = []
123 title << @project.name if @project
123 title << @project.name if @project
124 title << @html_header_title
124 title << @html_header_title
125 title << Setting.app_title
125 title << Setting.app_title
126 title.compact.join(' - ')
126 title.compact.join(' - ')
127 end
127 end
128
128
129 # format text according to system settings
129 # format text according to system settings
130 def textilizable(text, options = {})
130 def textilizable(text, options = {})
131 return "" if text.blank?
131 return "" if text.blank?
132
132
133 # when using an image link, try to use an attachment, if possible
133 # when using an image link, try to use an attachment, if possible
134 attachments = options[:attachments]
134 attachments = options[:attachments]
135 if attachments
135 if attachments
136 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
136 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
137 align = $1
137 align = $1
138 filename = $2
138 filename = $2
139 rf = Regexp.new(filename, Regexp::IGNORECASE)
139 rf = Regexp.new(filename, Regexp::IGNORECASE)
140 # search for the picture in attachments
140 # search for the picture in attachments
141 if found = attachments.detect { |att| att.filename =~ rf }
141 if found = attachments.detect { |att| att.filename =~ rf }
142 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
142 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
143 "!#{align}#{image_url}!"
143 "!#{align}#{image_url}!"
144 else
144 else
145 "!#{align}#{filename}!"
145 "!#{align}#{filename}!"
146 end
146 end
147 end
147 end
148 end
148 end
149
149
150 text = (Setting.text_formatting == 'textile') ?
150 text = (Setting.text_formatting == 'textile') ?
151 Redmine::WikiFormatting.to_html(text) : simple_format(auto_link(h(text)))
151 Redmine::WikiFormatting.to_html(text) : simple_format(auto_link(h(text)))
152
152
153 # different methods for formatting wiki links
153 # different methods for formatting wiki links
154 case options[:wiki_links]
154 case options[:wiki_links]
155 when :local
155 when :local
156 # used for local links to html files
156 # used for local links to html files
157 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
157 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
158 when :anchor
158 when :anchor
159 # used for single-file wiki export
159 # used for single-file wiki export
160 format_wiki_link = Proc.new {|project, title| "##{title}" }
160 format_wiki_link = Proc.new {|project, title| "##{title}" }
161 else
161 else
162 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
162 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
163 end
163 end
164
164
165 project = options[:project] || @project
165 project = options[:project] || @project
166
166
167 # turn wiki links into html links
167 # turn wiki links into html links
168 # example:
168 # example:
169 # [[mypage]]
169 # [[mypage]]
170 # [[mypage|mytext]]
170 # [[mypage|mytext]]
171 # wiki links can refer other project wikis, using project name or identifier:
171 # wiki links can refer other project wikis, using project name or identifier:
172 # [[project:]] -> wiki starting page
172 # [[project:]] -> wiki starting page
173 # [[project:|mytext]]
173 # [[project:|mytext]]
174 # [[project:mypage]]
174 # [[project:mypage]]
175 # [[project:mypage|mytext]]
175 # [[project:mypage|mytext]]
176 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
176 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
177 link_project = project
177 link_project = project
178 page = $1
178 page = $1
179 title = $3
179 title = $3
180 if page =~ /^([^\:]+)\:(.*)$/
180 if page =~ /^([^\:]+)\:(.*)$/
181 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
181 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
182 page = title || $2
182 page = title || $2
183 title = $1 if page.blank?
183 title = $1 if page.blank?
184 end
184 end
185
185
186 if link_project && link_project.wiki
186 if link_project && link_project.wiki
187 # check if page exists
187 # check if page exists
188 wiki_page = link_project.wiki.find_page(page)
188 wiki_page = link_project.wiki.find_page(page)
189 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
189 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
190 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
190 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
191 else
191 else
192 # project or wiki doesn't exist
192 # project or wiki doesn't exist
193 title || page
193 title || page
194 end
194 end
195 end
195 end
196
196
197 # turn issue and revision ids into links
197 # turn issue and revision ids into links
198 # example:
198 # example:
199 # #52 -> <a href="/issues/show/52">#52</a>
199 # #52 -> <a href="/issues/show/52">#52</a>
200 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (project.id is 6)
200 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (project.id is 6)
201 text = text.gsub(%r{([\s,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
201 text = text.gsub(%r{([\s,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
202 leading, otype, oid = $1, $2, $3
202 leading, otype, oid = $1, $2, $3
203 link = nil
203 link = nil
204 if otype == 'r'
204 if otype == 'r'
205 if project && (changeset = project.changesets.find_by_revision(oid))
205 if project && (changeset = project.changesets.find_by_revision(oid))
206 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
206 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
207 :title => truncate(changeset.comments, 100))
207 :title => truncate(changeset.comments, 100))
208 end
208 end
209 else
209 else
210 if issue = Issue.find_by_id(oid.to_i, :include => [:project, :status], :conditions => Project.visible_by(User.current))
210 if issue = Issue.find_by_id(oid.to_i, :include => [:project, :status], :conditions => Project.visible_by(User.current))
211 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
211 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
212 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
212 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
213 link = content_tag('del', link) if issue.closed?
213 link = content_tag('del', link) if issue.closed?
214 end
214 end
215 end
215 end
216 leading + (link || "#{otype}#{oid}")
216 leading + (link || "#{otype}#{oid}")
217 end
217 end
218
218
219 text
219 text
220 end
220 end
221
221
222 # Same as Rails' simple_format helper without using paragraphs
222 # Same as Rails' simple_format helper without using paragraphs
223 def simple_format_without_paragraph(text)
223 def simple_format_without_paragraph(text)
224 text.to_s.
224 text.to_s.
225 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
225 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
226 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
226 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
227 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
227 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
228 end
228 end
229
229
230 def error_messages_for(object_name, options = {})
230 def error_messages_for(object_name, options = {})
231 options = options.symbolize_keys
231 options = options.symbolize_keys
232 object = instance_variable_get("@#{object_name}")
232 object = instance_variable_get("@#{object_name}")
233 if object && !object.errors.empty?
233 if object && !object.errors.empty?
234 # build full_messages here with controller current language
234 # build full_messages here with controller current language
235 full_messages = []
235 full_messages = []
236 object.errors.each do |attr, msg|
236 object.errors.each do |attr, msg|
237 next if msg.nil?
237 next if msg.nil?
238 msg = msg.first if msg.is_a? Array
238 msg = msg.first if msg.is_a? Array
239 if attr == "base"
239 if attr == "base"
240 full_messages << l(msg)
240 full_messages << l(msg)
241 else
241 else
242 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
242 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
243 end
243 end
244 end
244 end
245 # retrieve custom values error messages
245 # retrieve custom values error messages
246 if object.errors[:custom_values]
246 if object.errors[:custom_values]
247 object.custom_values.each do |v|
247 object.custom_values.each do |v|
248 v.errors.each do |attr, msg|
248 v.errors.each do |attr, msg|
249 next if msg.nil?
249 next if msg.nil?
250 msg = msg.first if msg.is_a? Array
250 msg = msg.first if msg.is_a? Array
251 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
251 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
252 end
252 end
253 end
253 end
254 end
254 end
255 content_tag("div",
255 content_tag("div",
256 content_tag(
256 content_tag(
257 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
257 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
258 ) +
258 ) +
259 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
259 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
260 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
260 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
261 )
261 )
262 else
262 else
263 ""
263 ""
264 end
264 end
265 end
265 end
266
266
267 def lang_options_for_select(blank=true)
267 def lang_options_for_select(blank=true)
268 (blank ? [["(auto)", ""]] : []) +
268 (blank ? [["(auto)", ""]] : []) +
269 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
269 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
270 end
270 end
271
271
272 def label_tag_for(name, option_tags = nil, options = {})
272 def label_tag_for(name, option_tags = nil, options = {})
273 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
273 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
274 content_tag("label", label_text)
274 content_tag("label", label_text)
275 end
275 end
276
276
277 def labelled_tabular_form_for(name, object, options, &proc)
277 def labelled_tabular_form_for(name, object, options, &proc)
278 options[:html] ||= {}
278 options[:html] ||= {}
279 options[:html].store :class, "tabular"
279 options[:html].store :class, "tabular"
280 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
280 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
281 end
281 end
282
282
283 def check_all_links(form_name)
283 def check_all_links(form_name)
284 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
284 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
285 " | " +
285 " | " +
286 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
286 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
287 end
287 end
288
288
289 def calendar_for(field_id)
289 def calendar_for(field_id)
290 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
290 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
291 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
291 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
292 end
292 end
293
293
294 def wikitoolbar_for(field_id)
294 def wikitoolbar_for(field_id)
295 return '' unless Setting.text_formatting == 'textile'
295 return '' unless Setting.text_formatting == 'textile'
296 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
296 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
297 end
297 end
298
298
299 def content_for(name, content = nil, &block)
299 def content_for(name, content = nil, &block)
300 @has_content ||= {}
300 @has_content ||= {}
301 @has_content[name] = true
301 @has_content[name] = true
302 super(name, content, &block)
302 super(name, content, &block)
303 end
303 end
304
304
305 def has_content?(name)
305 def has_content?(name)
306 (@has_content && @has_content[name]) || false
306 (@has_content && @has_content[name]) || false
307 end
307 end
308 end
308 end
309
309
310 class TabularFormBuilder < ActionView::Helpers::FormBuilder
310 class TabularFormBuilder < ActionView::Helpers::FormBuilder
311 include GLoc
311 include GLoc
312
312
313 def initialize(object_name, object, template, options, proc)
313 def initialize(object_name, object, template, options, proc)
314 set_language_if_valid options.delete(:lang)
314 set_language_if_valid options.delete(:lang)
315 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
315 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
316 end
316 end
317
317
318 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
318 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
319 src = <<-END_SRC
319 src = <<-END_SRC
320 def #{selector}(field, options = {})
320 def #{selector}(field, options = {})
321 return super if options.delete :no_label
321 return super if options.delete :no_label
322 label_text = l(options[:label]) if options[:label]
322 label_text = l(options[:label]) if options[:label]
323 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
323 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
324 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
324 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
325 label = @template.content_tag("label", label_text,
325 label = @template.content_tag("label", label_text,
326 :class => (@object && @object.errors[field] ? "error" : nil),
326 :class => (@object && @object.errors[field] ? "error" : nil),
327 :for => (@object_name.to_s + "_" + field.to_s))
327 :for => (@object_name.to_s + "_" + field.to_s))
328 label + super
328 label + super
329 end
329 end
330 END_SRC
330 END_SRC
331 class_eval src, __FILE__, __LINE__
331 class_eval src, __FILE__, __LINE__
332 end
332 end
333
333
334 def select(field, choices, options = {}, html_options = {})
334 def select(field, choices, options = {}, html_options = {})
335 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
335 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
336 label = @template.content_tag("label", label_text,
336 label = @template.content_tag("label", label_text,
337 :class => (@object && @object.errors[field] ? "error" : nil),
337 :class => (@object && @object.errors[field] ? "error" : nil),
338 :for => (@object_name.to_s + "_" + field.to_s))
338 :for => (@object_name.to_s + "_" + field.to_s))
339 label + super
339 label + super
340 end
340 end
341
341
342 end
342 end
343
343
@@ -1,91 +1,104
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module IssuesHelper
18 module IssuesHelper
19
19
20 def render_issue_tooltip(issue)
21 @cached_label_start_date ||= l(:field_start_date)
22 @cached_label_due_date ||= l(:field_due_date)
23 @cached_label_assigned_to ||= l(:field_assigned_to)
24 @cached_label_priority ||= l(:field_priority)
25
26 link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" +
27 "<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 />" +
29 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
30 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
31 end
32
20 def show_detail(detail, no_html=false)
33 def show_detail(detail, no_html=false)
21 case detail.property
34 case detail.property
22 when 'attr'
35 when 'attr'
23 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
36 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
24 case detail.prop_key
37 case detail.prop_key
25 when 'due_date', 'start_date'
38 when 'due_date', 'start_date'
26 value = format_date(detail.value.to_date) if detail.value
39 value = format_date(detail.value.to_date) if detail.value
27 old_value = format_date(detail.old_value.to_date) if detail.old_value
40 old_value = format_date(detail.old_value.to_date) if detail.old_value
28 when 'status_id'
41 when 'status_id'
29 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
42 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
30 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
43 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
31 when 'assigned_to_id'
44 when 'assigned_to_id'
32 u = User.find_by_id(detail.value) and value = u.name if detail.value
45 u = User.find_by_id(detail.value) and value = u.name if detail.value
33 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
46 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
34 when 'priority_id'
47 when 'priority_id'
35 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
48 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
36 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
49 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
37 when 'category_id'
50 when 'category_id'
38 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
51 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
39 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
52 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
40 when 'fixed_version_id'
53 when 'fixed_version_id'
41 v = Version.find_by_id(detail.value) and value = v.name if detail.value
54 v = Version.find_by_id(detail.value) and value = v.name if detail.value
42 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
55 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
43 end
56 end
44 when 'cf'
57 when 'cf'
45 custom_field = CustomField.find_by_id(detail.prop_key)
58 custom_field = CustomField.find_by_id(detail.prop_key)
46 if custom_field
59 if custom_field
47 label = custom_field.name
60 label = custom_field.name
48 value = format_value(detail.value, custom_field.field_format) if detail.value
61 value = format_value(detail.value, custom_field.field_format) if detail.value
49 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
62 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
50 end
63 end
51 when 'attachment'
64 when 'attachment'
52 label = l(:label_attachment)
65 label = l(:label_attachment)
53 end
66 end
54
67
55 label ||= detail.prop_key
68 label ||= detail.prop_key
56 value ||= detail.value
69 value ||= detail.value
57 old_value ||= detail.old_value
70 old_value ||= detail.old_value
58
71
59 unless no_html
72 unless no_html
60 label = content_tag('strong', label)
73 label = content_tag('strong', label)
61 old_value = content_tag("i", h(old_value)) if detail.old_value
74 old_value = content_tag("i", h(old_value)) if detail.old_value
62 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
75 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
63 if detail.property == 'attachment' && !value.blank? && Attachment.find_by_id(detail.prop_key)
76 if detail.property == 'attachment' && !value.blank? && Attachment.find_by_id(detail.prop_key)
64 # Link to the attachment if it has not been removed
77 # Link to the attachment if it has not been removed
65 value = link_to(value, :controller => 'attachments', :action => 'download', :id => detail.prop_key)
78 value = link_to(value, :controller => 'attachments', :action => 'download', :id => detail.prop_key)
66 else
79 else
67 value = content_tag("i", h(value)) if value
80 value = content_tag("i", h(value)) if value
68 end
81 end
69 end
82 end
70
83
71 if !detail.value.blank?
84 if !detail.value.blank?
72 case detail.property
85 case detail.property
73 when 'attr', 'cf'
86 when 'attr', 'cf'
74 if !detail.old_value.blank?
87 if !detail.old_value.blank?
75 label + " " + l(:text_journal_changed, old_value, value)
88 label + " " + l(:text_journal_changed, old_value, value)
76 else
89 else
77 label + " " + l(:text_journal_set_to, value)
90 label + " " + l(:text_journal_set_to, value)
78 end
91 end
79 when 'attachment'
92 when 'attachment'
80 "#{label} #{value} #{l(:label_added)}"
93 "#{label} #{value} #{l(:label_added)}"
81 end
94 end
82 else
95 else
83 case detail.property
96 case detail.property
84 when 'attr', 'cf'
97 when 'attr', 'cf'
85 label + " " + l(:text_journal_deleted) + " (#{old_value})"
98 label + " " + l(:text_journal_deleted) + " (#{old_value})"
86 when 'attachment'
99 when 'attachment'
87 "#{label} #{old_value} #{l(:label_deleted)}"
100 "#{label} #{old_value} #{l(:label_deleted)}"
88 end
101 end
89 end
102 end
90 end
103 end
91 end
104 end
@@ -1,88 +1,88
1 <% cache(:year => @year, :month => @month, :tracker_ids => @selected_tracker_ids, :subprojects => params[:with_subprojects], :lang => current_language) do %>
1 <% cache(:year => @year, :month => @month, :tracker_ids => @selected_tracker_ids, :subprojects => params[:with_subprojects], :lang => current_language) do %>
2 <h2><%= l(:label_calendar) %>: <%= "#{month_name(@month).downcase} #{@year}" %></h2>
2 <h2><%= l(:label_calendar) %>: <%= "#{month_name(@month).downcase} #{@year}" %></h2>
3
3
4 <table width="100%">
4 <table width="100%">
5 <tr><td align="left">
5 <tr><td align="left">
6 <%= link_to_remote ('&#171; ' + (@month==1 ? "#{month_name(12)} #{@year-1}" : "#{month_name(@month-1)}")),
6 <%= link_to_remote ('&#171; ' + (@month==1 ? "#{month_name(12)} #{@year-1}" : "#{month_name(@month-1)}")),
7 {:update => "content", :url => { :year => (@month==1 ? @year-1 : @year), :month =>(@month==1 ? 12 : @month-1), :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] }},
7 {:update => "content", :url => { :year => (@month==1 ? @year-1 : @year), :month =>(@month==1 ? 12 : @month-1), :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] }},
8 {:href => url_for(:action => 'calendar', :year => (@month==1 ? @year-1 : @year), :month =>(@month==1 ? 12 : @month-1), :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects])}
8 {:href => url_for(:action => 'calendar', :year => (@month==1 ? @year-1 : @year), :month =>(@month==1 ? 12 : @month-1), :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects])}
9 %>
9 %>
10 </td><td align="right">
10 </td><td align="right">
11 <%= link_to_remote ((@month==12 ? "#{month_name(1)} #{@year+1}" : "#{month_name(@month+1)}") + ' &#187;'),
11 <%= link_to_remote ((@month==12 ? "#{month_name(1)} #{@year+1}" : "#{month_name(@month+1)}") + ' &#187;'),
12 {:update => "content", :url => { :year => (@month==12 ? @year+1 : @year), :month =>(@month==12 ? 1 : @month+1), :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] }},
12 {:update => "content", :url => { :year => (@month==12 ? @year+1 : @year), :month =>(@month==12 ? 1 : @month+1), :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] }},
13 {:href => url_for(:action => 'calendar', :year => (@month==12 ? @year+1 : @year), :month =>(@month==12 ? 1 : @month+1), :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects])}
13 {:href => url_for(:action => 'calendar', :year => (@month==12 ? @year+1 : @year), :month =>(@month==12 ? 1 : @month+1), :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects])}
14 %>
14 %>
15 </td></tr>
15 </td></tr>
16 </table>
16 </table>
17
17
18 <table class="cal">
18 <table class="cal">
19 <thead>
19 <thead>
20 <tr>
20 <tr>
21 <td></td>
21 <td></td>
22 <% 1.upto(7) do |d| %>
22 <% 1.upto(7) do |d| %>
23 <th style="width:14%"><%= day_name(d) %></th>
23 <th style="width:14%"><%= day_name(d) %></th>
24 <% end %>
24 <% end %>
25 </tr>
25 </tr>
26 </thead>
26 </thead>
27 <tbody>
27 <tbody>
28 <tr style="height:100px">
28 <tr style="height:100px">
29 <% day = @date_from
29 <% day = @date_from
30 while day <= @date_to
30 while day <= @date_to
31 if day.cwday == 1 %>
31 if day.cwday == 1 %>
32 <th><%= day.cweek %></th>
32 <th><%= day.cweek %></th>
33 <% end %>
33 <% end %>
34 <td valign="top" class="<%= day.month==@month ? "even" : "odd" %> <%= Date.today == day ? 'today' : '' %>" style="width:14%;">
34 <td valign="top" class="<%= day.month==@month ? "even" : "odd" %> <%= Date.today == day ? 'today' : '' %>" style="width:14%;">
35 <p class="textright"><%= day==Date.today ? "<b>#{day.day}</b>" : day.day %></p>
35 <p class="textright"><%= day==Date.today ? "<b>#{day.day}</b>" : day.day %></p>
36 <% ((@ending_events_by_days[day] || []) + (@starting_events_by_days[day] || [])).uniq.each do |i| %>
36 <% ((@ending_events_by_days[day] || []) + (@starting_events_by_days[day] || [])).uniq.each do |i| %>
37 <% if i.is_a? Issue %>
37 <% if i.is_a? Issue %>
38 <div class="tooltip">
38 <div class="tooltip">
39 <%= if day == i.start_date and day == i.due_date
39 <%= if day == i.start_date and day == i.due_date
40 image_tag('arrow_bw.png')
40 image_tag('arrow_bw.png')
41 elsif day == i.start_date
41 elsif day == i.start_date
42 image_tag('arrow_from.png')
42 image_tag('arrow_from.png')
43 elsif day == i.due_date
43 elsif day == i.due_date
44 image_tag('arrow_to.png')
44 image_tag('arrow_to.png')
45 end %>
45 end %>
46 <small>
46 <small>
47 <%= h("#{i.project.name} -") unless @project && @project == i.project %>
47 <%= h("#{i.project.name} -") unless @project && @project == i.project %>
48 <%= link_to_issue i %>:
48 <%= link_to_issue i %>:
49 <%= h(truncate(i.subject, 30)) %>
49 <%= h(truncate(i.subject, 30)) %>
50 </small>
50 </small>
51 <span class="tip">
51 <span class="tip">
52 <%= render :partial => "issues/tooltip", :locals => { :issue => i }%>
52 <%= render_issue_tooltip i %>
53 </span>
53 </span>
54 </div>
54 </div>
55 <% else %>
55 <% else %>
56 <small><%= link_to_version i, :class => "icon icon-package" %></small>
56 <small><%= link_to_version i, :class => "icon icon-package" %></small>
57 <% end %>
57 <% end %>
58 <% end %>
58 <% end %>
59 </td>
59 </td>
60 <%= '</tr><tr style="height:100px">' if day.cwday >= 7 and day!=@date_to %>
60 <%= '</tr><tr style="height:100px">' if day.cwday >= 7 and day!=@date_to %>
61 <%
61 <%
62 day = day + 1
62 day = day + 1
63 end %>
63 end %>
64 </tr>
64 </tr>
65 </tbody>
65 </tbody>
66 </table>
66 </table>
67
67
68 <%= image_tag 'arrow_from.png' %>&nbsp;&nbsp;<%= l(:text_tip_task_begin_day) %><br />
68 <%= image_tag 'arrow_from.png' %>&nbsp;&nbsp;<%= l(:text_tip_task_begin_day) %><br />
69 <%= image_tag 'arrow_to.png' %>&nbsp;&nbsp;<%= l(:text_tip_task_end_day) %><br />
69 <%= image_tag 'arrow_to.png' %>&nbsp;&nbsp;<%= l(:text_tip_task_end_day) %><br />
70 <%= image_tag 'arrow_bw.png' %>&nbsp;&nbsp;<%= l(:text_tip_task_begin_end_day) %><br />
70 <%= image_tag 'arrow_bw.png' %>&nbsp;&nbsp;<%= l(:text_tip_task_begin_end_day) %><br />
71 <% end %>
71 <% end %>
72
72
73 <% content_for :sidebar do %>
73 <% content_for :sidebar do %>
74 <h3><%= l(:label_calendar) %></h3>
74 <h3><%= l(:label_calendar) %></h3>
75
75
76 <% form_tag() do %>
76 <% form_tag() do %>
77 <p><%= select_month(@month, :prefix => "month", :discard_type => true) %>
77 <p><%= select_month(@month, :prefix => "month", :discard_type => true) %>
78 <%= select_year(@year, :prefix => "year", :discard_type => true) %></p>
78 <%= select_year(@year, :prefix => "year", :discard_type => true) %></p>
79
79
80 <% @trackers.each do |tracker| %>
80 <% @trackers.each do |tracker| %>
81 <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %> <%= tracker.name %></label><br />
81 <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %> <%= tracker.name %></label><br />
82 <% end %>
82 <% end %>
83 <% if @project.active_children.any? %>
83 <% if @project.active_children.any? %>
84 <br /><label><%= check_box_tag "with_subprojects", 1, params[:with_subprojects] %> <%=l(:label_subproject_plural)%></label>
84 <br /><label><%= check_box_tag "with_subprojects", 1, params[:with_subprojects] %> <%=l(:label_subproject_plural)%></label>
85 <% end %>
85 <% end %>
86 <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
86 <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
87 <% end %>
87 <% end %>
88 <% end %>
88 <% end %>
@@ -1,247 +1,247
1 <% zoom = 1
1 <% zoom = 1
2 @zoom.times { zoom = zoom * 2 }
2 @zoom.times { zoom = zoom * 2 }
3
3
4 subject_width = 330
4 subject_width = 330
5 header_heigth = 18
5 header_heigth = 18
6
6
7 headers_height = header_heigth
7 headers_height = header_heigth
8 show_weeks = false
8 show_weeks = false
9 show_days = false
9 show_days = false
10
10
11 if @zoom >1
11 if @zoom >1
12 show_weeks = true
12 show_weeks = true
13 headers_height = 2*header_heigth
13 headers_height = 2*header_heigth
14 if @zoom > 2
14 if @zoom > 2
15 show_days = true
15 show_days = true
16 headers_height = 3*header_heigth
16 headers_height = 3*header_heigth
17 end
17 end
18 end
18 end
19
19
20 g_width = (@date_to - @date_from + 1)*zoom
20 g_width = (@date_to - @date_from + 1)*zoom
21 g_height = [(20 * @events.length + 6)+150, 206].max
21 g_height = [(20 * @events.length + 6)+150, 206].max
22 t_height = g_height + headers_height
22 t_height = g_height + headers_height
23 %>
23 %>
24
24
25 <div class="contextual">
25 <div class="contextual">
26 </div>
26 </div>
27
27
28 <h2><%= l(:label_gantt) %></h2>
28 <h2><%= l(:label_gantt) %></h2>
29
29
30 <% form_tag(params.merge(:month => nil, :year => nil, :years => nil)) do %>
30 <% form_tag(params.merge(:month => nil, :year => nil, :years => nil)) do %>
31 <table width="100%">
31 <table width="100%">
32 <tr>
32 <tr>
33 <td align="left">
33 <td align="left">
34 <input type="text" name="months" size="2" value="<%= @months %>" />
34 <input type="text" name="months" size="2" value="<%= @months %>" />
35 <%= l(:label_months_from) %>
35 <%= l(:label_months_from) %>
36 <%= select_month(@month_from, :prefix => "month", :discard_type => true) %>
36 <%= select_month(@month_from, :prefix => "month", :discard_type => true) %>
37 <%= select_year(@year_from, :prefix => "year", :discard_type => true) %>
37 <%= select_year(@year_from, :prefix => "year", :discard_type => true) %>
38 <%= hidden_field_tag 'zoom', @zoom %>
38 <%= hidden_field_tag 'zoom', @zoom %>
39 <%= submit_tag l(:button_submit), :class => "button-small" %>
39 <%= submit_tag l(:button_submit), :class => "button-small" %>
40 </td>
40 </td>
41
41
42 <td align="right">
42 <td align="right">
43 <%= if @zoom < 4
43 <%= if @zoom < 4
44 link_to image_tag('zoom_in.png'), {:zoom => (@zoom+1), :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects]}
44 link_to image_tag('zoom_in.png'), {:zoom => (@zoom+1), :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects]}
45 else
45 else
46 image_tag 'zoom_in_g.png'
46 image_tag 'zoom_in_g.png'
47 end %>
47 end %>
48 <%= if @zoom > 1
48 <%= if @zoom > 1
49 link_to image_tag('zoom_out.png'),{:zoom => (@zoom-1), :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects]}
49 link_to image_tag('zoom_out.png'),{:zoom => (@zoom-1), :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects]}
50 else
50 else
51 image_tag 'zoom_out_g.png'
51 image_tag 'zoom_out_g.png'
52 end %>
52 end %>
53 </td>
53 </td>
54 </tr>
54 </tr>
55 </table>
55 </table>
56 <% end %>
56 <% end %>
57
57
58 <% cache(:year => @year_from, :month => @month_from, :months => @months, :zoom => @zoom, :tracker_ids => @selected_tracker_ids, :subprojects => params[:with_subprojects], :lang => current_language) do %>
58 <% cache(:year => @year_from, :month => @month_from, :months => @months, :zoom => @zoom, :tracker_ids => @selected_tracker_ids, :subprojects => params[:with_subprojects], :lang => current_language) do %>
59
59
60 <table width="100%" style="border:0; border-collapse: collapse;">
60 <table width="100%" style="border:0; border-collapse: collapse;">
61 <tr>
61 <tr>
62 <td style="width:<%= subject_width %>px;">
62 <td style="width:<%= subject_width %>px;">
63
63
64 <div style="position:relative;height:<%= t_height + 24 %>px;width:<%= subject_width + 1 %>px;">
64 <div style="position:relative;height:<%= t_height + 24 %>px;width:<%= subject_width + 1 %>px;">
65 <div style="right:-2px;width:<%= subject_width %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
65 <div style="right:-2px;width:<%= subject_width %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
66 <div style="right:-2px;width:<%= subject_width %>px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
66 <div style="right:-2px;width:<%= subject_width %>px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
67 <%
67 <%
68 #
68 #
69 # Tasks subjects
69 # Tasks subjects
70 #
70 #
71 top = headers_height + 8
71 top = headers_height + 8
72 @events.each do |i| %>
72 @events.each do |i| %>
73 <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:4px;overflow:hidden;"><small>
73 <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:4px;overflow:hidden;"><small>
74 <% if i.is_a? Issue %>
74 <% if i.is_a? Issue %>
75 <%= link_to_issue i %><%= " (#{i.project.name})" unless @project && @project == i.project %>:
75 <%= link_to_issue i %><%= " (#{i.project.name})" unless @project && @project == i.project %>:
76 <%=h i.subject %>
76 <%=h i.subject %>
77 <% else %>
77 <% else %>
78 <%= link_to_version i, :class => "icon icon-package" %>
78 <%= link_to_version i, :class => "icon icon-package" %>
79 <% end %>
79 <% end %>
80 </small></div>
80 </small></div>
81 <% top = top + 20
81 <% top = top + 20
82 end %>
82 end %>
83 </div>
83 </div>
84 </td>
84 </td>
85 <td>
85 <td>
86
86
87 <div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
87 <div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
88 <div style="width:<%= g_width-1 %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr">&nbsp;</div>
88 <div style="width:<%= g_width-1 %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr">&nbsp;</div>
89 <%
89 <%
90 #
90 #
91 # Months headers
91 # Months headers
92 #
92 #
93 month_f = @date_from
93 month_f = @date_from
94 left = 0
94 left = 0
95 height = (show_weeks ? header_heigth : header_heigth + g_height)
95 height = (show_weeks ? header_heigth : header_heigth + g_height)
96 @months.times do
96 @months.times do
97 width = ((month_f >> 1) - month_f) * zoom - 1
97 width = ((month_f >> 1) - month_f) * zoom - 1
98 %>
98 %>
99 <div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
99 <div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
100 <%= link_to "#{month_f.year}-#{month_f.month}", { :year => month_f.year, :month => month_f.month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] }, :title => "#{month_name(month_f.month)} #{month_f.year}"%>
100 <%= link_to "#{month_f.year}-#{month_f.month}", { :year => month_f.year, :month => month_f.month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] }, :title => "#{month_name(month_f.month)} #{month_f.year}"%>
101 </div>
101 </div>
102 <%
102 <%
103 left = left + width + 1
103 left = left + width + 1
104 month_f = month_f >> 1
104 month_f = month_f >> 1
105 end %>
105 end %>
106
106
107 <%
107 <%
108 #
108 #
109 # Weeks headers
109 # Weeks headers
110 #
110 #
111 if show_weeks
111 if show_weeks
112 left = 0
112 left = 0
113 height = (show_days ? header_heigth-1 : header_heigth-1 + g_height)
113 height = (show_days ? header_heigth-1 : header_heigth-1 + g_height)
114 if @date_from.cwday == 1
114 if @date_from.cwday == 1
115 # @date_from is monday
115 # @date_from is monday
116 week_f = @date_from
116 week_f = @date_from
117 else
117 else
118 # find next monday after @date_from
118 # find next monday after @date_from
119 week_f = @date_from + (7 - @date_from.cwday + 1)
119 week_f = @date_from + (7 - @date_from.cwday + 1)
120 width = (7 - @date_from.cwday + 1) * zoom-1
120 width = (7 - @date_from.cwday + 1) * zoom-1
121 %>
121 %>
122 <div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">&nbsp;</div>
122 <div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">&nbsp;</div>
123 <%
123 <%
124 left = left + width+1
124 left = left + width+1
125 end %>
125 end %>
126 <%
126 <%
127 while week_f <= @date_to
127 while week_f <= @date_to
128 width = (week_f + 6 <= @date_to) ? 7 * zoom -1 : (@date_to - week_f + 1) * zoom-1
128 width = (week_f + 6 <= @date_to) ? 7 * zoom -1 : (@date_to - week_f + 1) * zoom-1
129 %>
129 %>
130 <div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
130 <div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
131 <small><%= week_f.cweek if width >= 16 %></small>
131 <small><%= week_f.cweek if width >= 16 %></small>
132 </div>
132 </div>
133 <%
133 <%
134 left = left + width+1
134 left = left + width+1
135 week_f = week_f+7
135 week_f = week_f+7
136 end
136 end
137 end %>
137 end %>
138
138
139 <%
139 <%
140 #
140 #
141 # Days headers
141 # Days headers
142 #
142 #
143 if show_days
143 if show_days
144 left = 0
144 left = 0
145 height = g_height + header_heigth - 1
145 height = g_height + header_heigth - 1
146 wday = @date_from.cwday
146 wday = @date_from.cwday
147 (@date_to - @date_from + 1).to_i.times do
147 (@date_to - @date_from + 1).to_i.times do
148 width = zoom - 1
148 width = zoom - 1
149 %>
149 %>
150 <div style="left:<%= left %>px;top:37px;width:<%= width %>px;height:<%= height %>px;font-size:0.7em;<%= "background:#f1f1f1;" if wday > 5 %>" class="gantt_hdr">
150 <div style="left:<%= left %>px;top:37px;width:<%= width %>px;height:<%= height %>px;font-size:0.7em;<%= "background:#f1f1f1;" if wday > 5 %>" class="gantt_hdr">
151 <%= day_name(wday).first %>
151 <%= day_name(wday).first %>
152 </div>
152 </div>
153 <%
153 <%
154 left = left + width+1
154 left = left + width+1
155 wday = wday + 1
155 wday = wday + 1
156 wday = 1 if wday > 7
156 wday = 1 if wday > 7
157 end
157 end
158 end %>
158 end %>
159
159
160 <%
160 <%
161 #
161 #
162 # Tasks
162 # Tasks
163 #
163 #
164 top = headers_height + 10
164 top = headers_height + 10
165 @events.each do |i|
165 @events.each do |i|
166 if i.is_a? Issue
166 if i.is_a? Issue
167 i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from )
167 i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from )
168 i_end_date = (i.due_date <= @date_to ? i.due_date : @date_to )
168 i_end_date = (i.due_date <= @date_to ? i.due_date : @date_to )
169
169
170 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
170 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
171 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
171 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
172 i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
172 i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
173
173
174 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
174 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
175
175
176 i_left = ((i_start_date - @date_from)*zoom).floor
176 i_left = ((i_start_date - @date_from)*zoom).floor
177 i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders)
177 i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders)
178 d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width
178 d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width
179 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
179 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
180 %>
180 %>
181 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;" class="task task_todo">&nbsp;</div>
181 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;" class="task task_todo">&nbsp;</div>
182 <% if l_width > 0 %>
182 <% if l_width > 0 %>
183 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= l_width %>px;" class="task task_late">&nbsp;</div>
183 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= l_width %>px;" class="task task_late">&nbsp;</div>
184 <% end %>
184 <% end %>
185 <% if d_width > 0 %>
185 <% if d_width > 0 %>
186 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="task task_done">&nbsp;</div>
186 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="task task_done">&nbsp;</div>
187 <% end %>
187 <% end %>
188 <div style="top:<%= top %>px;left:<%= i_left + i_width + 5 %>px;background:#fff;" class="task">
188 <div style="top:<%= top %>px;left:<%= i_left + i_width + 5 %>px;background:#fff;" class="task">
189 <%= i.status.name %>
189 <%= i.status.name %>
190 <%= (i.done_ratio).to_i %>%
190 <%= (i.done_ratio).to_i %>%
191 </div>
191 </div>
192 <% # === tooltip === %>
192 <% # === tooltip === %>
193 <div class="tooltip" style="position: absolute;top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:12px;">
193 <div class="tooltip" style="position: absolute;top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:12px;">
194 <span class="tip">
194 <span class="tip">
195 <%= render :partial => "issues/tooltip", :locals => { :issue => i }%>
195 <%= render_issue_tooltip i %>
196 </span></div>
196 </span></div>
197 <% else
197 <% else
198 i_left = ((i.start_date - @date_from)*zoom).floor
198 i_left = ((i.start_date - @date_from)*zoom).floor
199 %>
199 %>
200 <div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone">&nbsp;</div>
200 <div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone">&nbsp;</div>
201 <div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task">
201 <div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task">
202 <strong><%= i.name %></strong>
202 <strong><%= i.name %></strong>
203 </div>
203 </div>
204 <% end %>
204 <% end %>
205 <% top = top + 20
205 <% top = top + 20
206 end %>
206 end %>
207
207
208 <% end # cache
208 <% end # cache
209 %>
209 %>
210
210
211 <%
211 <%
212 #
212 #
213 # Today red line (excluded from cache)
213 # Today red line (excluded from cache)
214 #
214 #
215 if Date.today >= @date_from and Date.today <= @date_to %>
215 if Date.today >= @date_from and Date.today <= @date_to %>
216 <div style="position: absolute;height:<%= g_height %>px;top:<%= headers_height + 1 %>px;left:<%= ((Date.today-@date_from+1)*zoom).floor()-1 %>px;width:10px;border-left: 1px dashed red;">&nbsp;</div>
216 <div style="position: absolute;height:<%= g_height %>px;top:<%= headers_height + 1 %>px;left:<%= ((Date.today-@date_from+1)*zoom).floor()-1 %>px;width:10px;border-left: 1px dashed red;">&nbsp;</div>
217 <% end %>
217 <% end %>
218
218
219 </div>
219 </div>
220 </td>
220 </td>
221 </tr>
221 </tr>
222 </table>
222 </table>
223
223
224 <table width="100%">
224 <table width="100%">
225 <tr>
225 <tr>
226 <td align="left"><%= link_to ('&#171; ' + l(:label_previous)), :year => (@date_from << @months).year, :month => (@date_from << @months).month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] %></td>
226 <td align="left"><%= link_to ('&#171; ' + l(:label_previous)), :year => (@date_from << @months).year, :month => (@date_from << @months).month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] %></td>
227 <td align="right"><%= link_to (l(:label_next) + ' &#187;'), :year => (@date_from >> @months).year, :month => (@date_from >> @months).month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] %></td>
227 <td align="right"><%= link_to (l(:label_next) + ' &#187;'), :year => (@date_from >> @months).year, :month => (@date_from >> @months).month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] %></td>
228 </tr>
228 </tr>
229 </table>
229 </table>
230
230
231 <div class="contextual"><%= l(:label_export_to) %>
231 <div class="contextual"><%= l(:label_export_to) %>
232 <%= link_to 'PDF', {:zoom => @zoom, :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects], :format => 'pdf'}, :class => 'icon icon-pdf' %>
232 <%= link_to 'PDF', {:zoom => @zoom, :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects], :format => 'pdf'}, :class => 'icon icon-pdf' %>
233 <%= link_to 'PNG', {:zoom => @zoom, :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects], :format => 'png'}, :class => 'icon icon-image' if respond_to?('gantt_image') %>
233 <%= link_to 'PNG', {:zoom => @zoom, :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects], :format => 'png'}, :class => 'icon icon-image' if respond_to?('gantt_image') %>
234 </div>
234 </div>
235
235
236 <% content_for :sidebar do %>
236 <% content_for :sidebar do %>
237 <h3><%= l(:label_gantt) %></h3>
237 <h3><%= l(:label_gantt) %></h3>
238 <% form_tag(params.merge(:tracker_ids => nil, :with_subprojects => nil)) do %>
238 <% form_tag(params.merge(:tracker_ids => nil, :with_subprojects => nil)) do %>
239 <% @trackers.each do |tracker| %>
239 <% @trackers.each do |tracker| %>
240 <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %> <%= tracker.name %></label><br />
240 <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %> <%= tracker.name %></label><br />
241 <% end %>
241 <% end %>
242 <% if @project.active_children.any? %>
242 <% if @project.active_children.any? %>
243 <br /><label><%= check_box_tag "with_subprojects", 1, params[:with_subprojects] %> <%=l(:label_subproject_plural)%></label>
243 <br /><label><%= check_box_tag "with_subprojects", 1, params[:with_subprojects] %> <%=l(:label_subproject_plural)%></label>
244 <% end %>
244 <% end %>
245 <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
245 <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
246 <% end %>
246 <% end %>
247 <% end %>
247 <% end %>
@@ -1,51 +1,51
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
3 </div>
3 </div>
4
4
5 <h2><%= l(:label_spent_time) %></h2>
5 <h2><%= l(:label_spent_time) %></h2>
6
6
7 <h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) if @project %>
7 <h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) if @project %>
8 <%= "/ " + link_to_issue(@issue) + h(": #{@issue.subject}") if @issue %></h3>
8 <%= "/ " + link_to_issue(@issue) + h(": #{@issue.subject}") if @issue %></h3>
9
9
10 <h3 class="textright"><%= l(:label_total) %>: <%= lwr(:label_f_hour, @total_hours) %></h3>
10 <h3 class="textright"><%= l(:label_total) %>: <%= lwr(:label_f_hour, @total_hours) %></h3>
11
11
12 <% unless @entries.empty? %>
12 <% unless @entries.empty? %>
13 <table class="list">
13 <table class="list">
14 <thead>
14 <thead>
15 <%= sort_header_tag('spent_on', :caption => l(:label_date)) %>
15 <%= sort_header_tag('spent_on', :caption => l(:label_date)) %>
16 <%= sort_header_tag('user_id', :caption => l(:label_member)) %>
16 <%= sort_header_tag('user_id', :caption => l(:label_member)) %>
17 <%= sort_header_tag('activity_id', :caption => l(:label_activity)) %>
17 <%= sort_header_tag('activity_id', :caption => l(:label_activity)) %>
18 <%= sort_header_tag('issue_id', :caption => l(:label_issue)) %>
18 <%= sort_header_tag('issue_id', :caption => l(:label_issue)) %>
19 <th><%= l(:field_comments) %></th>
19 <th><%= l(:field_comments) %></th>
20 <%= sort_header_tag('hours', :caption => l(:field_hours)) %>
20 <%= sort_header_tag('hours', :caption => l(:field_hours)) %>
21 <th></th>
21 <th></th>
22 </thead>
22 </thead>
23 <tbody>
23 <tbody>
24 <% @entries.each do |entry| %>
24 <% @entries.each do |entry| %>
25 <tr class="<%= cycle("odd", "even") %>">
25 <tr class="<%= cycle("odd", "even") %>">
26 <td align="center"><%= format_date(entry.spent_on) %></td>
26 <td align="center"><%= format_date(entry.spent_on) %></td>
27 <td align="center"><%= entry.user.name %></td>
27 <td align="center"><%= entry.user.name %></td>
28 <td align="center"><%= entry.activity.name %></td>
28 <td align="center"><%= entry.activity.name %></td>
29 <td align="center">
29 <td align="center">
30 <% if entry.issue %>
30 <% if entry.issue %>
31 <div class="tooltip">
31 <div class="tooltip">
32 <%= link_to "#{entry.issue.tracker.name} ##{entry.issue.id}", {:action => 'details', :issue_id => entry.issue } %>
32 <%= link_to_issue entry.issue %>
33 <span class="tip">
33 <span class="tip">
34 <%= render :partial => "issues/tooltip", :locals => { :issue => entry.issue }%>
34 <%= render_issue_tooltip entry.issue %>
35 </span>
35 </span>
36 </div>
36 </div>
37 <% end %>
37 <% end %>
38 </td>
38 </td>
39 <td><%=h entry.comments %></td>
39 <td><%=h entry.comments %></td>
40 <td align="center"><strong><%= entry.hours %></strong></td>
40 <td align="center"><strong><%= entry.hours %></strong></td>
41 <td align="center"><%= link_to_if_authorized(l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => entry}, :class => "icon icon-edit") if entry.user_id == @owner_id %></td>
41 <td align="center"><%= link_to_if_authorized(l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => entry}, :class => "icon icon-edit") if entry.user_id == @owner_id %></td>
42 </tr>
42 </tr>
43 <% end %>
43 <% end %>
44 </tbdoy>
44 </tbdoy>
45 </table>
45 </table>
46
46
47 <div class="contextual">
47 <div class="contextual">
48 <%= l(:label_export_to) %>
48 <%= l(:label_export_to) %>
49 <%= link_to 'CSV', params.update(:export => 'csv'), :class => 'icon icon-csv' %>
49 <%= link_to 'CSV', params.update(:export => 'csv'), :class => 'icon icon-csv' %>
50 </div>
50 </div>
51 <% end %> No newline at end of file
51 <% 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