@@ -95,11 +95,15 class ApplicationController < ActionController::Base | |||||
95 | end |
|
95 | end | |
96 | true |
|
96 | true | |
97 | end |
|
97 | end | |
|
98 | ||||
|
99 | def deny_access | |||
|
100 | User.current.logged? ? render_403 : require_login | |||
|
101 | end | |||
98 |
|
102 | |||
99 | # Authorize the user for the requested action |
|
103 | # Authorize the user for the requested action | |
100 | def authorize(ctrl = params[:controller], action = params[:action]) |
|
104 | def authorize(ctrl = params[:controller], action = params[:action]) | |
101 | allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project) |
|
105 | allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project) | |
102 | allowed ? true : (User.current.logged? ? render_403 : require_login) |
|
106 | allowed ? true : deny_access | |
103 | end |
|
107 | end | |
104 |
|
108 | |||
105 | # make sure that the user is a member of the project (or admin) if project is private |
|
109 | # make sure that the user is a member of the project (or admin) if project is private |
@@ -17,7 +17,8 | |||||
17 |
|
17 | |||
18 | class TimelogController < ApplicationController |
|
18 | class TimelogController < ApplicationController | |
19 | menu_item :issues |
|
19 | menu_item :issues | |
20 | before_filter :find_project, :authorize |
|
20 | before_filter :find_project, :authorize, :only => [:edit, :destroy] | |
|
21 | before_filter :find_optional_project, :only => [:report, :details] | |||
21 |
|
22 | |||
22 | verify :method => :post, :only => :destroy, :redirect_to => { :action => :details } |
|
23 | verify :method => :post, :only => :destroy, :redirect_to => { :action => :details } | |
23 |
|
24 | |||
@@ -53,11 +54,12 class TimelogController < ApplicationController | |||||
53 | } |
|
54 | } | |
54 |
|
55 | |||
55 | # Add list and boolean custom fields as available criterias |
|
56 | # Add list and boolean custom fields as available criterias | |
56 | @project.all_issue_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| |
|
57 | custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) | |
|
58 | custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| | |||
57 | @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)", |
|
59 | @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)", | |
58 | :format => cf.field_format, |
|
60 | :format => cf.field_format, | |
59 | :label => cf.name} |
|
61 | :label => cf.name} | |
60 | end |
|
62 | end if @project | |
61 |
|
63 | |||
62 | # Add list and boolean time entry custom fields |
|
64 | # Add list and boolean time entry custom fields | |
63 | TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| |
|
65 | TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| | |
@@ -83,9 +85,10 class TimelogController < ApplicationController | |||||
83 | sql << " FROM #{TimeEntry.table_name}" |
|
85 | sql << " FROM #{TimeEntry.table_name}" | |
84 | sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id" |
|
86 | sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id" | |
85 | sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id" |
|
87 | sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id" | |
86 | sql << " WHERE (%s)" % @project.project_condition(Setting.display_subprojects_issues?) |
|
88 | sql << " WHERE" | |
87 | sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries) |
|
89 | sql << " (%s) AND" % @project.project_condition(Setting.display_subprojects_issues?) if @project | |
88 | sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)] |
|
90 | sql << " (%s) AND" % Project.allowed_to_condition(User.current, :view_time_entries) | |
|
91 | sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)] | |||
89 | sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on" |
|
92 | sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on" | |
90 |
|
93 | |||
91 | @hours = ActiveRecord::Base.connection.select_all(sql) |
|
94 | @hours = ActiveRecord::Base.connection.select_all(sql) | |
@@ -138,8 +141,13 class TimelogController < ApplicationController | |||||
138 | sort_update |
|
141 | sort_update | |
139 |
|
142 | |||
140 | cond = ARCondition.new |
|
143 | cond = ARCondition.new | |
141 | cond << (@issue.nil? ? @project.project_condition(Setting.display_subprojects_issues?) : |
|
144 | if @project.nil? | |
142 | ["#{TimeEntry.table_name}.issue_id = ?", @issue.id]) |
|
145 | cond << Project.allowed_to_condition(User.current, :view_time_entries) | |
|
146 | elsif @issue.nil? | |||
|
147 | cond << @project.project_condition(Setting.display_subprojects_issues?) | |||
|
148 | else | |||
|
149 | cond << ["#{TimeEntry.table_name}.issue_id = ?", @issue.id] | |||
|
150 | end | |||
143 |
|
151 | |||
144 | retrieve_date_range |
|
152 | retrieve_date_range | |
145 | cond << ['spent_on BETWEEN ? AND ?', @from, @to] |
|
153 | cond << ['spent_on BETWEEN ? AND ?', @from, @to] | |
@@ -197,7 +205,7 class TimelogController < ApplicationController | |||||
197 | @time_entry.destroy |
|
205 | @time_entry.destroy | |
198 | flash[:notice] = l(:notice_successful_delete) |
|
206 | flash[:notice] = l(:notice_successful_delete) | |
199 | redirect_to :back |
|
207 | redirect_to :back | |
200 | rescue RedirectBackError |
|
208 | rescue ::ActionController::RedirectBackError | |
201 | redirect_to :action => 'details', :project_id => @time_entry.project |
|
209 | redirect_to :action => 'details', :project_id => @time_entry.project | |
202 | end |
|
210 | end | |
203 |
|
211 | |||
@@ -219,6 +227,16 private | |||||
219 | render_404 |
|
227 | render_404 | |
220 | end |
|
228 | end | |
221 |
|
229 | |||
|
230 | def find_optional_project | |||
|
231 | if !params[:issue_id].blank? | |||
|
232 | @issue = Issue.find(params[:issue_id]) | |||
|
233 | @project = @issue.project | |||
|
234 | elsif !params[:project_id].blank? | |||
|
235 | @project = Project.find(params[:project_id]) | |||
|
236 | end | |||
|
237 | deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true) | |||
|
238 | end | |||
|
239 | ||||
222 | # Retrieves the date range based on predefined ranges or specific from/to param dates |
|
240 | # Retrieves the date range based on predefined ranges or specific from/to param dates | |
223 | def retrieve_date_range |
|
241 | def retrieve_date_range | |
224 | @free_period = false |
|
242 | @free_period = false | |
@@ -261,7 +279,7 private | |||||
261 | end |
|
279 | end | |
262 |
|
280 | |||
263 | @from, @to = @to, @from if @from && @to && @from > @to |
|
281 | @from, @to = @to, @from if @from && @to && @from > @to | |
264 |
@from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => |
|
282 | @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) || Date.today) - 1 | |
265 |
@to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => |
|
283 | @to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) || Date.today) | |
266 | end |
|
284 | end | |
267 | end |
|
285 | end |
@@ -16,6 +16,14 | |||||
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 TimelogHelper |
|
18 | module TimelogHelper | |
|
19 | def render_timelog_breadcrumb | |||
|
20 | links = [] | |||
|
21 | links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil}) | |||
|
22 | links << link_to(h(@project), {:project_id => @project, :issue_id => nil}) if @project | |||
|
23 | links << link_to_issue(@issue) if @issue | |||
|
24 | breadcrumb links | |||
|
25 | end | |||
|
26 | ||||
19 | def activity_collection_for_select_options |
|
27 | def activity_collection_for_select_options | |
20 | activities = Enumeration::get_values('ACTI') |
|
28 | activities = Enumeration::get_values('ACTI') | |
21 | collection = [] |
|
29 | collection = [] |
@@ -243,7 +243,7 class User < ActiveRecord::Base | |||||
243 | elsif options[:global] |
|
243 | elsif options[:global] | |
244 | # authorize if user has at least one role that has this permission |
|
244 | # authorize if user has at least one role that has this permission | |
245 | roles = memberships.collect {|m| m.role}.uniq |
|
245 | roles = memberships.collect {|m| m.role}.uniq | |
246 | roles.detect {|r| r.allowed_to?(action)} |
|
246 | roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action)) | |
247 | else |
|
247 | else | |
248 | false |
|
248 | false | |
249 | end |
|
249 | end |
@@ -2,11 +2,9 | |||||
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 | <%= render_timelog_breadcrumb %> | |
6 |
|
6 | |||
7 | <% if @issue %> |
|
7 | <h2><%= l(:label_spent_time) %></h2> | |
8 | <h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) %> / <%= link_to_issue(@issue) %></h3> |
|
|||
9 | <% end %> |
|
|||
10 |
|
8 | |||
11 | <% form_remote_tag( :url => {}, :method => :get, :update => 'content' ) do %> |
|
9 | <% form_remote_tag( :url => {}, :method => :get, :update => 'content' ) do %> | |
12 | <%= hidden_field_tag 'project_id', params[:project_id] %> |
|
10 | <%= hidden_field_tag 'project_id', params[:project_id] %> |
@@ -2,6 +2,8 | |||||
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 | <%= render_timelog_breadcrumb %> | |||
|
6 | ||||
5 | <h2><%= l(:label_spent_time) %></h2> |
|
7 | <h2><%= l(:label_spent_time) %></h2> | |
6 |
|
8 | |||
7 | <% form_remote_tag(:url => {}, :update => 'content') do %> |
|
9 | <% form_remote_tag(:url => {}, :update => 'content') do %> |
@@ -20,7 +20,7 ActionController::Routing::Routes.draw do |map| | |||||
20 | map.connect 'projects/:project_id/news/:action', :controller => 'news' |
|
20 | map.connect 'projects/:project_id/news/:action', :controller => 'news' | |
21 | map.connect 'projects/:project_id/documents/:action', :controller => 'documents' |
|
21 | map.connect 'projects/:project_id/documents/:action', :controller => 'documents' | |
22 | map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards' |
|
22 | map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards' | |
23 | map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog' |
|
23 | map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/ | |
24 | map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages' |
|
24 | map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages' | |
25 |
|
25 | |||
26 | map.with_options :controller => 'repositories' do |omap| |
|
26 | map.with_options :controller => 'repositories' do |omap| |
@@ -78,7 +78,7 class TimelogControllerTest < Test::Unit::TestCase | |||||
78 | assert_equal 2, entry.user_id |
|
78 | assert_equal 2, entry.user_id | |
79 | end |
|
79 | end | |
80 |
|
80 | |||
81 | def destroy |
|
81 | def test_destroy | |
82 | @request.session[:user_id] = 2 |
|
82 | @request.session[:user_id] = 2 | |
83 | post :destroy, :id => 1 |
|
83 | post :destroy, :id => 1 | |
84 | assert_redirected_to 'projects/ecookbook/timelog/details' |
|
84 | assert_redirected_to 'projects/ecookbook/timelog/details' | |
@@ -91,6 +91,29 class TimelogControllerTest < Test::Unit::TestCase | |||||
91 | assert_template 'report' |
|
91 | assert_template 'report' | |
92 | end |
|
92 | end | |
93 |
|
93 | |||
|
94 | def test_report_all_projects | |||
|
95 | get :report | |||
|
96 | assert_response :success | |||
|
97 | assert_template 'report' | |||
|
98 | end | |||
|
99 | ||||
|
100 | def test_report_all_projects_denied | |||
|
101 | r = Role.anonymous | |||
|
102 | r.permissions.delete(:view_time_entries) | |||
|
103 | r.permissions_will_change! | |||
|
104 | r.save | |||
|
105 | get :report | |||
|
106 | assert_redirected_to '/account/login' | |||
|
107 | end | |||
|
108 | ||||
|
109 | def test_report_all_projects_one_criteria | |||
|
110 | get :report, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project'] | |||
|
111 | assert_response :success | |||
|
112 | assert_template 'report' | |||
|
113 | assert_not_nil assigns(:total_hours) | |||
|
114 | assert_equal "8.65", "%.2f" % assigns(:total_hours) | |||
|
115 | end | |||
|
116 | ||||
94 | def test_report_all_time |
|
117 | def test_report_all_time | |
95 | get :report, :project_id => 1, :criterias => ['project', 'issue'] |
|
118 | get :report, :project_id => 1, :criterias => ['project', 'issue'] | |
96 | assert_response :success |
|
119 | assert_response :success | |
@@ -148,7 +171,18 class TimelogControllerTest < Test::Unit::TestCase | |||||
148 | assert_not_nil assigns(:total_hours) |
|
171 | assert_not_nil assigns(:total_hours) | |
149 | assert_equal "0.00", "%.2f" % assigns(:total_hours) |
|
172 | assert_equal "0.00", "%.2f" % assigns(:total_hours) | |
150 | end |
|
173 | end | |
151 |
|
174 | |||
|
175 | def test_report_all_projects_csv_export | |||
|
176 | get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv" | |||
|
177 | assert_response :success | |||
|
178 | assert_equal 'text/csv', @response.content_type | |||
|
179 | lines = @response.body.chomp.split("\n") | |||
|
180 | # Headers | |||
|
181 | assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', lines.first | |||
|
182 | # Total row | |||
|
183 | assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last | |||
|
184 | end | |||
|
185 | ||||
152 | def test_report_csv_export |
|
186 | def test_report_csv_export | |
153 | get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv" |
|
187 | get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv" | |
154 | assert_response :success |
|
188 | assert_response :success | |
@@ -159,6 +193,14 class TimelogControllerTest < Test::Unit::TestCase | |||||
159 | # Total row |
|
193 | # Total row | |
160 | assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last |
|
194 | assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last | |
161 | end |
|
195 | end | |
|
196 | ||||
|
197 | def test_details_all_projects | |||
|
198 | get :details | |||
|
199 | assert_response :success | |||
|
200 | assert_template 'details' | |||
|
201 | assert_not_nil assigns(:total_hours) | |||
|
202 | assert_equal "162.90", "%.2f" % assigns(:total_hours) | |||
|
203 | end | |||
162 |
|
204 | |||
163 | def test_details_at_project_level |
|
205 | def test_details_at_project_level | |
164 | get :details, :project_id => 1 |
|
206 | get :details, :project_id => 1 | |
@@ -218,6 +260,14 class TimelogControllerTest < Test::Unit::TestCase | |||||
218 | assert assigns(:items).first.is_a?(TimeEntry) |
|
260 | assert assigns(:items).first.is_a?(TimeEntry) | |
219 | end |
|
261 | end | |
220 |
|
262 | |||
|
263 | def test_details_all_projects_csv_export | |||
|
264 | get :details, :format => 'csv' | |||
|
265 | assert_response :success | |||
|
266 | assert_equal 'text/csv', @response.content_type | |||
|
267 | assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment\n") | |||
|
268 | assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\"\n") | |||
|
269 | end | |||
|
270 | ||||
221 | def test_details_csv_export |
|
271 | def test_details_csv_export | |
222 | get :details, :project_id => 1, :format => 'csv' |
|
272 | get :details, :project_id => 1, :format => 'csv' | |
223 | assert_response :success |
|
273 | assert_response :success |
General Comments 0
You need to be logged in to leave comments.
Login now