Feature #31859 » 0001-Per-role-visibility-settings-for-spent-time-custom-f.patch
| app/models/time_entry.rb | ||
|---|---|---|
| 189 | 189 |
editable_custom_field_values(user).map(&:custom_field).uniq |
| 190 | 190 |
end |
| 191 | 191 | |
| 192 |
def visible_custom_field_values(user = nil) |
|
| 193 |
user ||= User.current |
|
| 194 |
custom_field_values.select do |value| |
|
| 195 |
value.custom_field.visible_by?(project, user) |
|
| 196 |
end |
|
| 197 |
end |
|
| 198 | ||
| 192 | 199 |
def assignable_users |
| 193 | 200 |
users = [] |
| 194 | 201 |
if project |
| app/models/time_entry_custom_field.rb | ||
|---|---|---|
| 21 | 21 |
def type_name |
| 22 | 22 |
:label_spent_time |
| 23 | 23 |
end |
| 24 |
end |
|
| 25 | 24 | |
| 25 |
def visible_by?(project, user=User.current) |
|
| 26 |
super || (roles & user.roles_for_project(project)).present? |
|
| 27 |
end |
|
| 28 | ||
| 29 |
def validate_custom_field |
|
| 30 |
super |
|
| 31 |
errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) unless visible? || roles.present?
|
|
| 32 |
end |
|
| 33 |
end |
|
| app/views/custom_fields/_form.html.erb | ||
|---|---|---|
| 31 | 31 |
</div> |
| 32 | 32 | |
| 33 | 33 |
<div class="splitcontentright"> |
| 34 |
<div class="box tabular"> |
|
| 35 | 34 |
<% case @custom_field.class.name |
| 36 | 35 |
when "IssueCustomField" %> |
| 37 |
<p><%= f.check_box :is_required %></p> |
|
| 38 |
<% if @custom_field.format.is_filter_supported %> |
|
| 39 |
<p><%= f.check_box :is_filter %></p> |
|
| 40 |
<% end %> |
|
| 41 |
<% if @custom_field.format.searchable_supported %> |
|
| 42 |
<p><%= f.check_box :searchable %></p> |
|
| 43 |
<% end %> |
|
| 36 |
<div class="box tabular"> |
|
| 37 |
<p><%= f.check_box :is_required %></p> |
|
| 38 |
<% if @custom_field.format.is_filter_supported %> |
|
| 39 |
<p><%= f.check_box :is_filter %></p> |
|
| 40 |
<% end %> |
|
| 41 |
<% if @custom_field.format.searchable_supported %> |
|
| 42 |
<p><%= f.check_box :searchable %></p> |
|
| 43 |
<% end %> |
|
| 44 |
</div> |
|
| 45 |
<%= render :partial => 'visibility_by_role_selector' %> |
|
| 44 | 46 | |
| 45 | 47 |
<% when "UserCustomField" %> |
| 46 |
<p><%= f.check_box :is_required %></p> |
|
| 47 |
<p><%= f.check_box :visible %></p> |
|
| 48 |
<p><%= f.check_box :editable %></p> |
|
| 49 |
<% if @custom_field.format.is_filter_supported %> |
|
| 50 |
<p><%= f.check_box :is_filter %></p> |
|
| 51 |
<% end %> |
|
| 48 |
<div class="box tabular"> |
|
| 49 |
<p><%= f.check_box :is_required %></p> |
|
| 50 |
<p><%= f.check_box :visible %></p> |
|
| 51 |
<p><%= f.check_box :editable %></p> |
|
| 52 |
<% if @custom_field.format.is_filter_supported %> |
|
| 53 |
<p><%= f.check_box :is_filter %></p> |
|
| 54 |
<% end %> |
|
| 55 |
</div> |
|
| 52 | 56 | |
| 53 | 57 |
<% when "ProjectCustomField" %> |
| 54 |
<p><%= f.check_box :is_required %></p> |
|
| 55 |
<p><%= f.check_box :visible %></p> |
|
| 56 |
<% if @custom_field.format.searchable_supported %> |
|
| 57 |
<p><%= f.check_box :searchable %></p> |
|
| 58 |
<% end %> |
|
| 59 |
<% if @custom_field.format.is_filter_supported %> |
|
| 60 |
<p><%= f.check_box :is_filter %></p> |
|
| 61 |
<% end %> |
|
| 58 |
<div class="box tabular"> |
|
| 59 |
<p><%= f.check_box :is_required %></p> |
|
| 60 |
<p><%= f.check_box :visible %></p> |
|
| 61 |
<% if @custom_field.format.searchable_supported %> |
|
| 62 |
<p><%= f.check_box :searchable %></p> |
|
| 63 |
<% end %> |
|
| 64 |
<% if @custom_field.format.is_filter_supported %> |
|
| 65 |
<p><%= f.check_box :is_filter %></p> |
|
| 66 |
<% end %> |
|
| 67 |
</div> |
|
| 62 | 68 | |
| 63 | 69 |
<% when "VersionCustomField" %> |
| 64 |
<p><%= f.check_box :is_required %></p> |
|
| 65 |
<% if @custom_field.format.is_filter_supported %> |
|
| 66 |
<p><%= f.check_box :is_filter %></p> |
|
| 67 |
<% end %> |
|
| 70 |
<div class="box tabular"> |
|
| 71 |
<p><%= f.check_box :is_required %></p> |
|
| 72 |
<% if @custom_field.format.is_filter_supported %> |
|
| 73 |
<p><%= f.check_box :is_filter %></p> |
|
| 74 |
<% end %> |
|
| 75 |
</div> |
|
| 68 | 76 | |
| 69 | 77 |
<% when "GroupCustomField" %> |
| 70 |
<p><%= f.check_box :is_required %></p> |
|
| 71 |
<% if @custom_field.format.is_filter_supported %> |
|
| 72 |
<p><%= f.check_box :is_filter %></p> |
|
| 73 |
<% end %> |
|
| 78 |
<div class="box tabular"> |
|
| 79 |
<p><%= f.check_box :is_required %></p> |
|
| 80 |
<% if @custom_field.format.is_filter_supported %> |
|
| 81 |
<p><%= f.check_box :is_filter %></p> |
|
| 82 |
<% end %> |
|
| 83 |
</div> |
|
| 74 | 84 | |
| 75 | 85 |
<% when "TimeEntryCustomField" %> |
| 76 |
<p><%= f.check_box :is_required %></p> |
|
| 77 |
<% if @custom_field.format.is_filter_supported %> |
|
| 78 |
<p><%= f.check_box :is_filter %></p> |
|
| 79 |
<% end %> |
|
| 86 |
<div class="box tabular"> |
|
| 87 |
<p><%= f.check_box :is_required %></p> |
|
| 88 |
<% if @custom_field.format.is_filter_supported %> |
|
| 89 |
<p><%= f.check_box :is_filter %></p> |
|
| 90 |
<% end %> |
|
| 91 |
</div> |
|
| 92 |
<%= render :partial => 'visibility_by_role_selector' %> |
|
| 80 | 93 | |
| 81 | 94 |
<% else %> |
| 95 |
<div class="box tabular"> |
|
| 82 | 96 |
<p><%= f.check_box :is_required %></p> |
| 83 | ||
| 97 |
</div> |
|
| 84 | 98 |
<% end %> |
| 85 | 99 |
<%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
|
| 86 |
</div> |
|
| 87 | 100 | |
| 88 | 101 |
<% if @custom_field.is_a?(IssueCustomField) %> |
| 89 | 102 | |
| 90 |
<fieldset class="box tabular"><legend><%= l(:field_visible) %></legend> |
|
| 91 |
<label class="block"> |
|
| 92 |
<%= radio_button_tag 'custom_field[visible]', 1, @custom_field.visible?, :id => 'custom_field_visible_on', |
|
| 93 |
:data => {:disables => '.custom_field_role input'} %>
|
|
| 94 |
<%= l(:label_visibility_public) %> |
|
| 95 |
</label> |
|
| 96 |
<label class="block"> |
|
| 97 |
<%= radio_button_tag 'custom_field[visible]', 0, !@custom_field.visible?, :id => 'custom_field_visible_off', |
|
| 98 |
:data => {:enables => '.custom_field_role input'} %>
|
|
| 99 |
<%= l(:label_visibility_roles) %>: |
|
| 100 |
</label> |
|
| 101 |
<% role_ids = @custom_field.role_ids %> |
|
| 102 |
<% Role.givable.sorted.each do |role| %> |
|
| 103 |
<label class="block custom_field_role" style="padding-left:2em;"> |
|
| 104 |
<%= check_box_tag 'custom_field[role_ids][]', role.id, role_ids.include?(role.id), :id => nil %> |
|
| 105 |
<%= role.name %> |
|
| 106 |
</label> |
|
| 107 |
<% end %> |
|
| 108 |
<%= hidden_field_tag 'custom_field[role_ids][]', '' %> |
|
| 109 |
</fieldset> |
|
| 110 | ||
| 111 | 103 |
<fieldset class="box" id="custom_field_tracker_ids"><legend><%= toggle_checkboxes_link("#custom_field_tracker_ids input[type=checkbox]") %><%=l(:label_tracker_plural)%></legend>
|
| 112 | 104 |
<% tracker_ids = @custom_field.tracker_ids %> |
| 113 | 105 |
<% Tracker.sorted.each do |tracker| %> |
| app/views/custom_fields/_visibility_by_role_selector.html.erb | ||
|---|---|---|
| 1 |
<fieldset class="box tabular"><legend><%= l(:field_visible) %></legend> |
|
| 2 |
<label class="block"> |
|
| 3 |
<%= radio_button_tag 'custom_field[visible]', 1, @custom_field.visible?, :id => 'custom_field_visible_on', |
|
| 4 |
:data => {:disables => '.custom_field_role input'} %>
|
|
| 5 |
<%= l(:label_visibility_public) %> |
|
| 6 |
</label> |
|
| 7 |
<label class="block"> |
|
| 8 |
<%= radio_button_tag 'custom_field[visible]', 0, !@custom_field.visible?, :id => 'custom_field_visible_off', |
|
| 9 |
:data => {:enables => '.custom_field_role input'} %>
|
|
| 10 |
<%= l(:label_visibility_roles) %>: |
|
| 11 |
</label> |
|
| 12 |
<% role_ids = @custom_field.role_ids %> |
|
| 13 |
<% Role.givable.sorted.each do |role| %> |
|
| 14 |
<label class="block custom_field_role" style="padding-left:2em;"> |
|
| 15 |
<%= check_box_tag 'custom_field[role_ids][]', role.id, role_ids.include?(role.id), :id => nil %> |
|
| 16 |
<%= role.name %> |
|
| 17 |
</label> |
|
| 18 |
<% end %> |
|
| 19 |
<%= hidden_field_tag 'custom_field[role_ids][]', '' %> |
|
| 20 |
</fieldset> |
|
| app/views/timelog/_form.html.erb | ||
|---|---|---|
| 23 | 23 |
<p><%= f.hours_field :hours, :size => 6, :required => true %></p> |
| 24 | 24 |
<p><%= f.text_field :comments, :size => 100, :maxlength => 1024, :required => Setting.timelog_required_fields.include?('comments') %></p>
|
| 25 | 25 |
<p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p> |
| 26 |
<% @time_entry.custom_field_values.each do |value| %> |
|
| 26 |
<% @time_entry.editable_custom_field_values.each do |value| %>
|
|
| 27 | 27 |
<p><%= custom_field_tag_with_label :time_entry, value %></p> |
| 28 | 28 |
<% end %> |
| 29 | 29 |
<%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
|
| app/views/timelog/index.api.rsb | ||
|---|---|---|
| 12 | 12 |
api.created_on time_entry.created_on |
| 13 | 13 |
api.updated_on time_entry.updated_on |
| 14 | 14 | |
| 15 |
render_api_custom_values time_entry.custom_field_values, api |
|
| 15 |
render_api_custom_values time_entry.visible_custom_field_values, api
|
|
| 16 | 16 |
end |
| 17 | 17 |
end |
| 18 | 18 |
end |
| test/functional/custom_fields_controller_test.rb | ||
|---|---|---|
| 95 | 95 |
assert_select 'option[value=user]', :text => 'User' |
| 96 | 96 |
assert_select 'option[value=version]', :text => 'Version' |
| 97 | 97 |
end |
| 98 | ||
| 99 |
# Visibility |
|
| 100 |
assert_select 'input[type=radio][name=?]', 'custom_field[visible]', 2 |
|
| 101 |
assert_select 'input[type=checkbox][name=?]', 'custom_field[role_ids][]', 3 |
|
| 102 | ||
| 98 | 103 |
assert_select 'input[type=checkbox][name=?]', 'custom_field[project_ids][]', Project.count |
| 99 | 104 |
assert_select 'input[type=hidden][name=?]', 'custom_field[project_ids][]', 1 |
| 100 | 105 |
assert_select 'input[type=hidden][name=type][value=IssueCustomField]' |
| 101 | 106 |
end |
| 102 | 107 |
end |
| 103 | 108 | |
| 109 |
def test_new_time_entry_custom_field |
|
| 110 |
get :new, :params => {
|
|
| 111 |
:type => 'TimeEntryCustomField' |
|
| 112 |
} |
|
| 113 |
assert_response :success |
|
| 114 | ||
| 115 |
assert_select 'form#custom_field_form' do |
|
| 116 |
assert_select 'select#custom_field_field_format[name=?]', 'custom_field[field_format]' do |
|
| 117 |
assert_select 'option[value=user]', :text => 'User' |
|
| 118 |
assert_select 'option[value=version]', :text => 'Version' |
|
| 119 |
end |
|
| 120 | ||
| 121 |
# Visibility |
|
| 122 |
assert_select 'input[type=radio][name=?]', 'custom_field[visible]', 2 |
|
| 123 |
assert_select 'input[type=checkbox][name=?]', 'custom_field[role_ids][]', 3 |
|
| 124 | ||
| 125 |
assert_select 'input[type=hidden][name=type][value=TimeEntryCustomField]' |
|
| 126 |
end |
|
| 127 |
end |
|
| 128 | ||
| 104 | 129 |
def test_new_time_entry_custom_field_should_not_show_trackers_and_projects |
| 105 | 130 |
get :new, :params => {
|
| 106 | 131 |
:type => 'TimeEntryCustomField' |
| test/functional/timelog_custom_fields_visibility_test.rb | ||
|---|---|---|
| 34 | 34 |
:workflows, |
| 35 | 35 |
:custom_fields, :custom_values, :custom_fields_trackers |
| 36 | 36 | |
| 37 |
def setup |
|
| 38 |
field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all}
|
|
| 39 |
@fields = [] |
|
| 40 |
@fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) |
|
| 41 |
@fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) |
|
| 42 |
@fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) |
|
| 43 |
@issue = Issue.generate!( |
|
| 44 |
:author_id => 1, |
|
| 45 |
:project_id => 1, |
|
| 46 |
:tracker_id => 1, |
|
| 47 |
:custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'}
|
|
| 48 |
) |
|
| 49 |
TimeEntry.generate!(:issue => @issue) |
|
| 50 | ||
| 51 |
@user_with_role_on_other_project = User.generate! |
|
| 52 |
User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) |
|
| 53 | ||
| 54 |
@users_to_test = {
|
|
| 55 |
User.find(1) => [@field1, @field2, @field3], |
|
| 56 |
User.find(3) => [@field1, @field2], |
|
| 57 |
@user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 |
|
| 58 |
User.generate! => [@field1], |
|
| 59 |
User.anonymous => [@field1] |
|
| 60 |
} |
|
| 61 | ||
| 62 |
Member.where(:project_id => 1).each do |member| |
|
| 63 |
member.destroy unless @users_to_test.keys.include?(member.principal) |
|
| 64 |
end |
|
| 65 |
end |
|
| 66 | ||
| 67 | 37 |
def test_index_should_show_visible_custom_fields_only |
| 38 |
prepare_test_data |
|
| 39 | ||
| 68 | 40 |
@users_to_test.each do |user, fields| |
| 69 | 41 |
@request.session[:user_id] = user.id |
| 70 | 42 |
get :index, :params => {
|
| ... | ... | |
| 83 | 55 |
end |
| 84 | 56 | |
| 85 | 57 |
def test_index_as_csv_should_show_visible_custom_fields_only |
| 58 |
prepare_test_data |
|
| 59 | ||
| 86 | 60 |
@users_to_test.each do |user, fields| |
| 87 | 61 |
@request.session[:user_id] = user.id |
| 88 | 62 |
get :index, :params => {
|
| ... | ... | |
| 102 | 76 |
end |
| 103 | 77 | |
| 104 | 78 |
def test_index_with_partial_custom_field_visibility_should_show_visible_custom_fields_only |
| 79 |
prepare_test_data |
|
| 80 | ||
| 105 | 81 |
Issue.delete_all |
| 106 | 82 |
TimeEntry.delete_all |
| 107 | 83 |
CustomValue.delete_all |
| ... | ... | |
| 131 | 107 |
assert_select 'td', :text => "ValueC" |
| 132 | 108 |
assert_select 'td', :text => "ValueB", :count => 0 |
| 133 | 109 |
end |
| 110 | ||
| 111 |
def test_edit_should_not_show_custom_fields_not_visible_for_user |
|
| 112 |
time_entry_cf = TimeEntryCustomField.find(10) |
|
| 113 |
time_entry_cf.visible = false |
|
| 114 |
time_entry_cf.role_ids = [2] |
|
| 115 |
time_entry_cf.save! |
|
| 116 | ||
| 117 |
@request.session[:user_id] = 2 |
|
| 118 | ||
| 119 |
get :edit, :params => {
|
|
| 120 |
:id => 3, |
|
| 121 |
:project_id => 1 |
|
| 122 |
} |
|
| 123 | ||
| 124 |
assert_response :success |
|
| 125 |
assert_select 'select#time_entry_custom_field_values_10', 0 |
|
| 126 |
end |
|
| 127 | ||
| 128 |
private |
|
| 129 | ||
| 130 |
def prepare_test_data |
|
| 131 |
field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all}
|
|
| 132 |
@fields = [] |
|
| 133 |
@fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) |
|
| 134 |
@fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) |
|
| 135 |
@fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) |
|
| 136 |
@issue = Issue.generate!( |
|
| 137 |
:author_id => 1, |
|
| 138 |
:project_id => 1, |
|
| 139 |
:tracker_id => 1, |
|
| 140 |
:custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'}
|
|
| 141 |
) |
|
| 142 |
TimeEntry.generate!(:issue => @issue) |
|
| 143 | ||
| 144 |
@user_with_role_on_other_project = User.generate! |
|
| 145 |
User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) |
|
| 146 | ||
| 147 |
@users_to_test = {
|
|
| 148 |
User.find(1) => [@field1, @field2, @field3], |
|
| 149 |
User.find(3) => [@field1, @field2], |
|
| 150 |
@user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 |
|
| 151 |
User.generate! => [@field1], |
|
| 152 |
User.anonymous => [@field1] |
|
| 153 |
} |
|
| 154 | ||
| 155 |
Member.where(:project_id => 1).each do |member| |
|
| 156 |
member.destroy unless @users_to_test.keys.include?(member.principal) |
|
| 157 |
end |
|
| 158 |
end |
|
| 134 | 159 |
end |
| test/functional/timelog_report_test.rb | ||
|---|---|---|
| 138 | 138 | |
| 139 | 139 |
def test_hidden_custom_fields_should_not_be_proposed |
| 140 | 140 |
TimeEntryCustomField.create!(name: 'shown', field_format: 'list', possible_values: ['value1', 'value2'], visible: true) |
| 141 |
TimeEntryCustomField.create!(name: 'Hidden', field_format: 'list', possible_values: ['value1', 'value2'], visible: false) |
|
| 141 |
TimeEntryCustomField.create!(name: 'Hidden', field_format: 'list', possible_values: ['value1', 'value2'], visible: false, role_ids: [3])
|
|
| 142 | 142 | |
| 143 | 143 |
get :report, :params => {:project_id => 1}
|
| 144 | 144 |
assert_response :success |