##// END OF EJS Templates
Adds a role setting that viewing all or own time entries (#8929)....
Jean-Philippe Lang -
r13893:6659aad3ef65
parent child
Show More
@@ -0,0 +1,9
1 class AddRolesTimeEntriesVisibility < ActiveRecord::Migration
2 def self.up
3 add_column :roles, :time_entries_visibility, :string, :limit => 30, :default => 'all', :null => false
4 end
5
6 def self.down
7 remove_column :roles, :time_entries_visibility
8 end
9 end
@@ -143,7 +143,7 class ProjectsController < ApplicationController
143 @open_issues_by_tracker = Issue.visible.open.where(cond).group(:tracker).count
143 @open_issues_by_tracker = Issue.visible.open.where(cond).group(:tracker).count
144 @total_issues_by_tracker = Issue.visible.where(cond).group(:tracker).count
144 @total_issues_by_tracker = Issue.visible.where(cond).group(:tracker).count
145
145
146 if User.current.allowed_to?(:view_time_entries, @project)
146 if User.current.allowed_to_view_all_time_entries?(@project)
147 @total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f
147 @total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f
148 end
148 end
149
149
@@ -39,6 +39,11 class Role < ActiveRecord::Base
39 ['own', :label_issues_visibility_own]
39 ['own', :label_issues_visibility_own]
40 ]
40 ]
41
41
42 TIME_ENTRIES_VISIBILITY_OPTIONS = [
43 ['all', :label_time_entries_visibility_all],
44 ['own', :label_time_entries_visibility_own]
45 ]
46
42 USERS_VISIBILITY_OPTIONS = [
47 USERS_VISIBILITY_OPTIONS = [
43 ['all', :label_users_visibility_all],
48 ['all', :label_users_visibility_all],
44 ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects]
49 ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects]
@@ -75,6 +80,9 class Role < ActiveRecord::Base
75 validates_inclusion_of :users_visibility,
80 validates_inclusion_of :users_visibility,
76 :in => USERS_VISIBILITY_OPTIONS.collect(&:first),
81 :in => USERS_VISIBILITY_OPTIONS.collect(&:first),
77 :if => lambda {|role| role.respond_to?(:users_visibility) && role.users_visibility_changed?}
82 :if => lambda {|role| role.respond_to?(:users_visibility) && role.users_visibility_changed?}
83 validates_inclusion_of :time_entries_visibility,
84 :in => TIME_ENTRIES_VISIBILITY_OPTIONS.collect(&:first),
85 :if => lambda {|role| role.respond_to?(:time_entries_visibility) && role.time_entries_visibility_changed?}
78
86
79 # Copies attributes from another role, arg can be an id or a Role
87 # Copies attributes from another role, arg can be an id or a Role
80 def copy_from(arg, options={})
88 def copy_from(arg, options={})
@@ -46,7 +46,7 class TimeEntry < ActiveRecord::Base
46
46
47 scope :visible, lambda {|*args|
47 scope :visible, lambda {|*args|
48 joins(:project).
48 joins(:project).
49 where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args))
49 where(TimeEntry.visible_condition(args.shift || User.current, *args))
50 }
50 }
51 scope :on_issue, lambda {|issue|
51 scope :on_issue, lambda {|issue|
52 joins(:issue).
52 joins(:issue).
@@ -55,6 +55,32 class TimeEntry < ActiveRecord::Base
55
55
56 safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
56 safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
57
57
58 # Returns a SQL conditions string used to find all time entries visible by the specified user
59 def self.visible_condition(user, options={})
60 Project.allowed_to_condition(user, :view_time_entries, options) do |role, user|
61 if role.time_entries_visibility == 'all'
62 nil
63 elsif role.time_entries_visibility == 'own' && user.id && user.logged?
64 "#{table_name}.user_id = #{user.id}"
65 else
66 '1=0'
67 end
68 end
69 end
70
71 # Returns true if user or current user is allowed to view the time entry
72 def visible?(user=nil)
73 (user || User.current).allowed_to?(:view_time_entries, self.project) do |role, user|
74 if role.time_entries_visibility == 'all'
75 true
76 elsif role.time_entries_visibility == 'own'
77 self.user == user
78 else
79 false
80 end
81 end
82 end
83
58 def initialize(attributes=nil, *args)
84 def initialize(attributes=nil, *args)
59 super
85 super
60 if new_record? && self.activity.nil?
86 if new_record? && self.activity.nil?
@@ -116,7 +142,9 class TimeEntry < ActiveRecord::Base
116
142
117 # Returns true if the time entry can be edited by usr, otherwise false
143 # Returns true if the time entry can be edited by usr, otherwise false
118 def editable_by?(usr)
144 def editable_by?(usr)
119 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
145 visible?(usr) && (
146 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
147 )
120 end
148 end
121
149
122 # Returns the custom_field_values that can be edited by the given user
150 # Returns the custom_field_values that can be edited by the given user
@@ -634,6 +634,12 class User < Principal
634 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
634 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
635 end
635 end
636
636
637 def allowed_to_view_all_time_entries?(context)
638 allowed_to?(:view_time_entries, context) do |role, user|
639 role.time_entries_visibility == 'all'
640 end
641 end
642
637 # Returns true if the user is allowed to delete the user's own account
643 # Returns true if the user is allowed to delete the user's own account
638 def own_account_deletable?
644 def own_account_deletable?
639 Setting.unsubscribe? &&
645 Setting.unsubscribe? &&
@@ -62,7 +62,7
62 rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
62 rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
63 end
63 end
64 end
64 end
65 if User.current.allowed_to?(:view_time_entries, @project)
65 if User.current.allowed_to_view_all_time_entries?(@project)
66 if @issue.total_spent_hours > 0
66 if @issue.total_spent_hours > 0
67 rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
67 rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
68 end
68 end
@@ -1,6 +1,8
1 <% if @total_hours.present? %>
1 <% if User.current.allowed_to?(:view_time_entries, @project) %>
2 <h3><%= l(:label_spent_time) %></h3>
2 <h3><%= l(:label_spent_time) %></h3>
3 <p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p>
3 <% if @total_hours.present? %>
4 <p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p>
5 <% end %>
4 <p>
6 <p>
5 <% if User.current.allowed_to?(:log_time, @project) %>
7 <% if User.current.allowed_to?(:log_time, @project) %>
6 <%= link_to l(:button_log_time), new_project_time_entry_path(@project) %> |
8 <%= link_to l(:button_log_time), new_project_time_entry_path(@project) %> |
@@ -10,6 +10,10
10 <p><%= f.select :issues_visibility, Role::ISSUES_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p>
10 <p><%= f.select :issues_visibility, Role::ISSUES_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p>
11 <% end %>
11 <% end %>
12
12
13 <% unless @role.anonymous? %>
14 <p><%= f.select :time_entries_visibility, Role::TIME_ENTRIES_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p>
15 <% end %>
16
13 <p><%= f.select :users_visibility, Role::USERS_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p>
17 <p><%= f.select :users_visibility, Role::USERS_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p>
14
18
15 <% if @role.new_record? && @roles.any? %>
19 <% if @role.new_record? && @roles.any? %>
@@ -19,7 +19,7
19 <th><%= l(:field_estimated_hours) %></th>
19 <th><%= l(:field_estimated_hours) %></th>
20 <td class="total-hours"><%= html_hours(l_hours(@version.estimated_hours)) %></td>
20 <td class="total-hours"><%= html_hours(l_hours(@version.estimated_hours)) %></td>
21 </tr>
21 </tr>
22 <% if User.current.allowed_to?(:view_time_entries, @project) %>
22 <% if User.current.allowed_to_view_all_time_entries?(@project) %>
23 <tr>
23 <tr>
24 <th><%= l(:label_spent_time) %></th>
24 <th><%= l(:label_spent_time) %></th>
25 <td class="total-hours"><%= html_hours(l_hours(@version.spent_hours)) %></td>
25 <td class="total-hours"><%= html_hours(l_hours(@version.spent_hours)) %></td>
@@ -341,6 +341,7 en:
341 field_must_change_passwd: Must change password at next logon
341 field_must_change_passwd: Must change password at next logon
342 field_default_status: Default status
342 field_default_status: Default status
343 field_users_visibility: Users visibility
343 field_users_visibility: Users visibility
344 field_time_entries_visibility: Time logs visibility
344
345
345 setting_app_title: Application title
346 setting_app_title: Application title
346 setting_app_subtitle: Application subtitle
347 setting_app_subtitle: Application subtitle
@@ -361,6 +361,7 fr:
361 field_must_change_passwd: Doit changer de mot de passe Γ  la prochaine connexion
361 field_must_change_passwd: Doit changer de mot de passe Γ  la prochaine connexion
362 field_default_status: Statut par dΓ©faut
362 field_default_status: Statut par dΓ©faut
363 field_users_visibility: VisibilitΓ© des utilisateurs
363 field_users_visibility: VisibilitΓ© des utilisateurs
364 field_time_entries_visibility: VisibilitΓ© du temps passΓ©
364
365
365 setting_app_title: Titre de l'application
366 setting_app_title: Titre de l'application
366 setting_app_subtitle: Sous-titre de l'application
367 setting_app_subtitle: Sous-titre de l'application
@@ -962,6 +963,8 fr:
962 label_disable_notifications: DΓ©sactiver les notifications
963 label_disable_notifications: DΓ©sactiver les notifications
963 label_blank_value: non renseignΓ©
964 label_blank_value: non renseignΓ©
964 label_parent_task_attributes: Attributs des tΓ’ches parentes
965 label_parent_task_attributes: Attributs des tΓ’ches parentes
966 label_time_entries_visibility_all: Tous les temps passΓ©s
967 label_time_entries_visibility_own: Ses propres temps passΓ©s
965
968
966 button_login: Connexion
969 button_login: Connexion
967 button_submit: Soumettre
970 button_submit: Soumettre
@@ -27,6 +27,38 class TimeEntryTest < ActiveSupport::TestCase
27 :groups_users,
27 :groups_users,
28 :enabled_modules
28 :enabled_modules
29
29
30 def test_visibility_with_permission_to_view_all_time_entries
31 user = User.generate!
32 role = Role.generate!(:permissions => [:view_time_entries], :time_entries_visibility => 'all')
33 Role.non_member.remove_permission! :view_time_entries
34 project = Project.find(1)
35 User.add_to_project user, project, role
36 own = TimeEntry.generate! :user => user, :project => project
37 other = TimeEntry.generate! :user => User.find(2), :project => project
38
39 assert TimeEntry.visible(user).find_by_id(own.id)
40 assert TimeEntry.visible(user).find_by_id(other.id)
41
42 assert own.visible?(user)
43 assert other.visible?(user)
44 end
45
46 def test_visibility_with_permission_to_view_own_time_entries
47 user = User.generate!
48 role = Role.generate!(:permissions => [:view_time_entries], :time_entries_visibility => 'own')
49 Role.non_member.remove_permission! :view_time_entries
50 project = Project.find(1)
51 User.add_to_project user, project, role
52 own = TimeEntry.generate! :user => user, :project => project
53 other = TimeEntry.generate! :user => User.find(2), :project => project
54
55 assert TimeEntry.visible(user).find_by_id(own.id)
56 assert_nil TimeEntry.visible(user).find_by_id(other.id)
57
58 assert own.visible?(user)
59 assert_equal false, other.visible?(user)
60 end
61
30 def test_hours_format
62 def test_hours_format
31 assertions = { "2" => 2.0,
63 assertions = { "2" => 2.0,
32 "21.1" => 21.1,
64 "21.1" => 21.1,
General Comments 0
You need to be logged in to leave comments. Login now