]> source.dussan.org Git - redmine.git/commitdiff
Per role visibility settings for spent time custom fields (#31859).
authorGo MAEDA <maeda@farend.jp>
Fri, 9 Aug 2019 09:31:16 +0000 (09:31 +0000)
committerGo MAEDA <maeda@farend.jp>
Fri, 9 Aug 2019 09:31:16 +0000 (09:31 +0000)
Patch by Marius BALTEANU.

git-svn-id: http://svn.redmine.org/redmine/trunk@18358 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/models/issue_query.rb
app/models/time_entry.rb
app/models/time_entry_custom_field.rb
app/views/custom_fields/_form.html.erb
app/views/custom_fields/_visibility_by_role_selector.html.erb [new file with mode: 0644]
app/views/timelog/_form.html.erb
app/views/timelog/index.api.rsb
test/functional/custom_fields_controller_test.rb
test/functional/timelog_custom_fields_visibility_test.rb
test/functional/timelog_report_test.rb

index 5245d5ba05bc392419fedfd85b49f411cc82facd..7701cbfe22ba340d43b35eed75025a01bb410d3d 100644 (file)
@@ -40,8 +40,10 @@ class IssueQuery < Query
     QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date", :groupable => true),
     QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
     QueryColumn.new(:total_estimated_hours,
-      :sortable => -> { "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
-        " WHERE #{Issue.visible_condition(User.current).gsub(/\bissues\b/, 'subtasks')} AND subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)" },
+      :sortable => -> {
+                     "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
+        " WHERE #{Issue.visible_condition(User.current).gsub(/\bissues\b/, 'subtasks')} AND subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)"
+                   },
       :default_order => 'desc'),
     QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
     TimestampQueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc', :groupable => true),
index 9701622c8f040f97a5b748c18861b5d66b621dac..d2661d078eaf118a4ff4b8330d2e8b54573d13d2 100644 (file)
@@ -189,6 +189,13 @@ class TimeEntry < ActiveRecord::Base
     editable_custom_field_values(user).map(&:custom_field).uniq
   end
 
+  def visible_custom_field_values(user = nil)
+    user ||= User.current
+    custom_field_values.select do |value|
+      value.custom_field.visible_by?(project, user)
+    end
+  end
+
   def assignable_users
     users = []
     if project
index 9ef820efb47e724a8a6b93ffd574792a60801d28..601e1b962c29e69f59e7f6b3f5db5d91cd0f4eaa 100644 (file)
@@ -21,5 +21,13 @@ class TimeEntryCustomField < CustomField
   def type_name
     :label_spent_time
   end
-end
 
+  def visible_by?(project, user=User.current)
+    super || (roles & user.roles_for_project(project)).present?
+  end
+
+  def validate_custom_field
+    super
+    errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) unless visible? || roles.present?
+  end
+end
index cac457ce2cb3f439ad3ef627f95e540b91decab1..e428a27c6ee7987027036d1b8c63afdc54e74ec5 100644 (file)
 </div>
 
 <div class="splitcontentright">
-<div class="box tabular">
 <% case @custom_field.class.name
 when "IssueCustomField" %>
-    <p><%= f.check_box :is_required %></p>
-    <% if @custom_field.format.is_filter_supported %>
-    <p><%= f.check_box :is_filter %></p>
-    <% end %>
-    <% if @custom_field.format.searchable_supported %>
-    <p><%= f.check_box :searchable %></p>
-    <% end %>
+    <div class="box tabular">
+      <p><%= f.check_box :is_required %></p>
+      <% if @custom_field.format.is_filter_supported %>
+      <p><%= f.check_box :is_filter %></p>
+      <% end %>
+      <% if @custom_field.format.searchable_supported %>
+      <p><%= f.check_box :searchable %></p>
+      <% end %>
+    </div>
+    <%= render :partial => 'visibility_by_role_selector' %>
 
 <% when "UserCustomField" %>
-    <p><%= f.check_box :is_required %></p>
-    <p><%= f.check_box :visible %></p>
-    <p><%= f.check_box :editable %></p>
-    <% if @custom_field.format.is_filter_supported %>
-    <p><%= f.check_box :is_filter %></p>
-    <% end %>
+    <div class="box tabular">
+      <p><%= f.check_box :is_required %></p>
+      <p><%= f.check_box :visible %></p>
+      <p><%= f.check_box :editable %></p>
+      <% if @custom_field.format.is_filter_supported %>
+      <p><%= f.check_box :is_filter %></p>
+      <% end %>
+    </div>
 
 <% when "ProjectCustomField" %>
-    <p><%= f.check_box :is_required %></p>
-    <p><%= f.check_box :visible %></p>
-    <% if @custom_field.format.searchable_supported %>
-    <p><%= f.check_box :searchable %></p>
-    <% end %>
-    <% if @custom_field.format.is_filter_supported %>
-    <p><%= f.check_box :is_filter %></p>
-    <% end %>
+    <div class="box tabular">
+      <p><%= f.check_box :is_required %></p>
+      <p><%= f.check_box :visible %></p>
+      <% if @custom_field.format.searchable_supported %>
+      <p><%= f.check_box :searchable %></p>
+      <% end %>
+      <% if @custom_field.format.is_filter_supported %>
+      <p><%= f.check_box :is_filter %></p>
+      <% end %>
+    </div>
 
 <% when "VersionCustomField" %>
-    <p><%= f.check_box :is_required %></p>
-    <% if @custom_field.format.is_filter_supported %>
-    <p><%= f.check_box :is_filter %></p>
-    <% end %>
+    <div class="box tabular">
+      <p><%= f.check_box :is_required %></p>
+      <% if @custom_field.format.is_filter_supported %>
+      <p><%= f.check_box :is_filter %></p>
+      <% end %>
+    </div>
 
 <% when "GroupCustomField" %>
-    <p><%= f.check_box :is_required %></p>
-    <% if @custom_field.format.is_filter_supported %>
-    <p><%= f.check_box :is_filter %></p>
-    <% end %>
+    <div class="box tabular">
+      <p><%= f.check_box :is_required %></p>
+      <% if @custom_field.format.is_filter_supported %>
+      <p><%= f.check_box :is_filter %></p>
+      <% end %>
+    </div>
 
 <% when "TimeEntryCustomField" %>
-    <p><%= f.check_box :is_required %></p>
-    <% if @custom_field.format.is_filter_supported %>
-    <p><%= f.check_box :is_filter %></p>
-    <% end %>
+    <div class="box tabular">
+      <p><%= f.check_box :is_required %></p>
+      <% if @custom_field.format.is_filter_supported %>
+      <p><%= f.check_box :is_filter %></p>
+      <% end %>
+    </div>
+    <%= render :partial => 'visibility_by_role_selector' %>
 
 <% else %>
+  <div class="box tabular">
     <p><%= f.check_box :is_required %></p>
-
+  </div>
 <% end %>
 <%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
-</div>
 
 <% if @custom_field.is_a?(IssueCustomField) %>
 
-  <fieldset class="box tabular"><legend><%= l(:field_visible) %></legend>
-    <label class="block">
-      <%= radio_button_tag 'custom_field[visible]', 1, @custom_field.visible?, :id => 'custom_field_visible_on',
-            :data => {:disables => '.custom_field_role input'} %>
-      <%= l(:label_visibility_public) %>
-    </label>
-    <label class="block">
-      <%= radio_button_tag 'custom_field[visible]', 0, !@custom_field.visible?, :id => 'custom_field_visible_off',
-            :data => {:enables => '.custom_field_role input'} %>
-      <%= l(:label_visibility_roles) %>:
-    </label>
-    <% role_ids = @custom_field.role_ids %>
-    <% Role.givable.sorted.each do |role| %>
-      <label class="block custom_field_role" style="padding-left:2em;">
-        <%= check_box_tag 'custom_field[role_ids][]', role.id, role_ids.include?(role.id), :id => nil %>
-        <%= role.name %>
-      </label>
-    <% end %>
-    <%= hidden_field_tag 'custom_field[role_ids][]', '' %>
-  </fieldset>
-
   <fieldset class="box" id="custom_field_tracker_ids"><legend><%= toggle_checkboxes_link("#custom_field_tracker_ids input[type=checkbox]") %><%=l(:label_tracker_plural)%></legend>
   <% tracker_ids = @custom_field.tracker_ids %>
   <% Tracker.sorted.each do |tracker| %>
diff --git a/app/views/custom_fields/_visibility_by_role_selector.html.erb b/app/views/custom_fields/_visibility_by_role_selector.html.erb
new file mode 100644 (file)
index 0000000..552bc79
--- /dev/null
@@ -0,0 +1,20 @@
+<fieldset class="box tabular"><legend><%= l(:field_visible) %></legend>
+  <label class="block">
+    <%= radio_button_tag 'custom_field[visible]', 1, @custom_field.visible?, :id => 'custom_field_visible_on',
+          :data => {:disables => '.custom_field_role input'} %>
+    <%= l(:label_visibility_public) %>
+  </label>
+  <label class="block">
+    <%= radio_button_tag 'custom_field[visible]', 0, !@custom_field.visible?, :id => 'custom_field_visible_off',
+          :data => {:enables => '.custom_field_role input'} %>
+    <%= l(:label_visibility_roles) %>:
+  </label>
+  <% role_ids = @custom_field.role_ids %>
+  <% Role.givable.sorted.each do |role| %>
+    <label class="block custom_field_role" style="padding-left:2em;">
+      <%= check_box_tag 'custom_field[role_ids][]', role.id, role_ids.include?(role.id), :id => nil %>
+      <%= role.name %>
+    </label>
+  <% end %>
+  <%= hidden_field_tag 'custom_field[role_ids][]', '' %>
+</fieldset>
index 691da9ed1cde5f185c869404eef61b3d52ba2b05..bfeb67337beb3f81f5fe8ce95b490d292a0fbac4 100644 (file)
@@ -23,7 +23,7 @@
   <p><%= f.hours_field :hours, :size => 6, :required => true %></p>
   <p><%= f.text_field :comments, :size => 100, :maxlength => 1024, :required => Setting.timelog_required_fields.include?('comments') %></p>
   <p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p>
-  <% @time_entry.custom_field_values.each do |value| %>
+  <% @time_entry.editable_custom_field_values.each do |value| %>
     <p><%= custom_field_tag_with_label :time_entry, value %></p>
   <% end %>
   <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
index d281e89b2f2fb16e9362be386a8e0fb680345b6e..976714869260e3db63a85693c43dd95fc18c0df0 100644 (file)
@@ -12,7 +12,7 @@ api.array :time_entries, api_meta(:total_count => @entry_count, :offset => @offs
       api.created_on time_entry.created_on
       api.updated_on time_entry.updated_on
 
-      render_api_custom_values time_entry.custom_field_values, api
+      render_api_custom_values time_entry.visible_custom_field_values, api
     end
   end
 end
index 9745e2396395ae45b2601d9971d7bee14b0c8ecb..e59d408ef5ca0a91c08c379d955e37ffcf923acb 100644 (file)
@@ -95,12 +95,37 @@ class CustomFieldsControllerTest < Redmine::ControllerTest
         assert_select 'option[value=user]', :text => 'User'
         assert_select 'option[value=version]', :text => 'Version'
       end
+
+      # Visibility
+      assert_select 'input[type=radio][name=?]', 'custom_field[visible]', 2
+      assert_select 'input[type=checkbox][name=?]', 'custom_field[role_ids][]', 3
+
       assert_select 'input[type=checkbox][name=?]', 'custom_field[project_ids][]', Project.count
       assert_select 'input[type=hidden][name=?]', 'custom_field[project_ids][]', 1
       assert_select 'input[type=hidden][name=type][value=IssueCustomField]'
     end
   end
 
+  def test_new_time_entry_custom_field
+    get :new, :params => {
+        :type => 'TimeEntryCustomField'
+      }
+    assert_response :success
+
+    assert_select 'form#custom_field_form' do
+      assert_select 'select#custom_field_field_format[name=?]', 'custom_field[field_format]' do
+        assert_select 'option[value=user]', :text => 'User'
+        assert_select 'option[value=version]', :text => 'Version'
+      end
+
+      # Visibility
+      assert_select 'input[type=radio][name=?]', 'custom_field[visible]', 2
+      assert_select 'input[type=checkbox][name=?]', 'custom_field[role_ids][]', 3
+
+      assert_select 'input[type=hidden][name=type][value=TimeEntryCustomField]'
+    end
+  end
+
   def test_new_time_entry_custom_field_should_not_show_trackers_and_projects
     get :new, :params => {
         :type => 'TimeEntryCustomField'
index 4304e2d6eb3dce88ffab232d8b4f129b88023021..d43f3a35047fb79ff1cddb3457f6714af5db12db 100644 (file)
@@ -34,37 +34,9 @@ class TimelogCustomFieldsVisibilityTest < Redmine::ControllerTest
            :workflows,
            :custom_fields, :custom_values, :custom_fields_trackers
 
-  def setup
-    field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all}
-    @fields = []
-    @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true)))
-    @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2])))
-    @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3])))
-    @issue = Issue.generate!(
-      :author_id => 1,
-      :project_id => 1,
-      :tracker_id => 1,
-      :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'}
-    )
-    TimeEntry.generate!(:issue => @issue)
-
-    @user_with_role_on_other_project = User.generate!
-    User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3))
-
-    @users_to_test = {
-      User.find(1) => [@field1, @field2, @field3],
-      User.find(3) => [@field1, @field2],
-      @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1
-      User.generate! => [@field1],
-      User.anonymous => [@field1]
-    }
-
-    Member.where(:project_id => 1).each do |member|
-      member.destroy unless @users_to_test.keys.include?(member.principal)
-    end
-  end
-
   def test_index_should_show_visible_custom_fields_only
+    prepare_test_data
+
     @users_to_test.each do |user, fields|
       @request.session[:user_id] = user.id
       get :index, :params => {
@@ -83,6 +55,8 @@ class TimelogCustomFieldsVisibilityTest < Redmine::ControllerTest
   end
 
   def test_index_as_csv_should_show_visible_custom_fields_only
+    prepare_test_data
+
     @users_to_test.each do |user, fields|
       @request.session[:user_id] = user.id
       get :index, :params => {
@@ -102,6 +76,8 @@ class TimelogCustomFieldsVisibilityTest < Redmine::ControllerTest
   end
 
   def test_index_with_partial_custom_field_visibility_should_show_visible_custom_fields_only
+    prepare_test_data
+
     Issue.delete_all
     TimeEntry.delete_all
     CustomValue.delete_all
@@ -131,4 +107,53 @@ class TimelogCustomFieldsVisibilityTest < Redmine::ControllerTest
     assert_select 'td', :text => "ValueC"
     assert_select 'td', :text => "ValueB", :count => 0
   end
+
+  def test_edit_should_not_show_custom_fields_not_visible_for_user
+    time_entry_cf = TimeEntryCustomField.find(10)
+    time_entry_cf.visible = false
+    time_entry_cf.role_ids = [2]
+    time_entry_cf.save!
+
+    @request.session[:user_id] = 2
+
+    get :edit, :params => {
+      :id => 3,
+      :project_id => 1
+    }
+
+    assert_response :success
+    assert_select 'select#time_entry_custom_field_values_10', 0
+  end
+
+  private
+
+  def prepare_test_data
+    field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all}
+    @fields = []
+    @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true)))
+    @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2])))
+    @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3])))
+    @issue = Issue.generate!(
+      :author_id => 1,
+      :project_id => 1,
+      :tracker_id => 1,
+      :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'}
+    )
+    TimeEntry.generate!(:issue => @issue)
+
+    @user_with_role_on_other_project = User.generate!
+    User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3))
+
+    @users_to_test = {
+      User.find(1) => [@field1, @field2, @field3],
+      User.find(3) => [@field1, @field2],
+      @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1
+      User.generate! => [@field1],
+      User.anonymous => [@field1]
+    }
+
+    Member.where(:project_id => 1).each do |member|
+      member.destroy unless @users_to_test.keys.include?(member.principal)
+    end
+  end
 end
index 00432302dade51e91257888a07e0ac54de335c98..a643522d3e5c1e627ede3c4f846f6116ccc9d6f0 100644 (file)
@@ -138,7 +138,7 @@ class TimelogReportTest < Redmine::ControllerTest
 
   def test_hidden_custom_fields_should_not_be_proposed
     TimeEntryCustomField.create!(name: 'shown', field_format: 'list', possible_values: ['value1', 'value2'], visible: true)
-    TimeEntryCustomField.create!(name: 'Hidden', field_format: 'list', possible_values: ['value1', 'value2'], visible: false)
+    TimeEntryCustomField.create!(name: 'Hidden', field_format: 'list', possible_values: ['value1', 'value2'], visible: false, role_ids: [3])
 
     get :report, :params => {:project_id => 1}
     assert_response :success