summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGo MAEDA <maeda@farend.jp>2023-04-11 09:11:40 +0000
committerGo MAEDA <maeda@farend.jp>2023-04-11 09:11:40 +0000
commit9546dfa5e3aac247db62ea3a0d8e09672ed8196b (patch)
treee6a8d55193cc05f4470836502ea13fd6abe7fef5
parent87f3352d3a64e2b6a89aa7036852627eb3953e6f (diff)
downloadredmine-9546dfa5e3aac247db62ea3a0d8e09672ed8196b.tar.gz
redmine-9546dfa5e3aac247db62ea3a0d8e09672ed8196b.zip
Add Parent task filter and column to Spent time (#37623).
Patch by Mizuki ISHIKAWA. git-svn-id: https://svn.redmine.org/redmine/trunk@22177 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r--app/helpers/queries_helper.rb10
-rw-r--r--app/models/time_entry_query.rb29
-rw-r--r--test/functional/timelog_controller_test.rb46
-rw-r--r--test/unit/query_test.rb26
4 files changed, 106 insertions, 5 deletions
diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb
index 20dae6fd4..662e22ee8 100644
--- a/app/helpers/queries_helper.rb
+++ b/app/helpers/queries_helper.rb
@@ -26,15 +26,15 @@ module QueriesHelper
ungrouped = []
grouped = {label_string: [], label_date: [], label_time_tracking: [], label_attachment: []}
query.available_filters.map do |field, field_options|
- if field_options[:type] == :relation
+ if field =~ /^(.+)\./
+ # association filters
+ group = "field_#{$1}".to_sym
+ elsif field_options[:type] == :relation
group = :label_relations
elsif field_options[:type] == :tree
group = query.is_a?(IssueQuery) ? :label_relations : nil
elsif /^cf_\d+\./.match?(field)
group = (field_options[:through] || field_options[:field]).try(:name)
- elsif field =~ /^(.+)\./
- # association filters
- group = "field_#{$1}".to_sym
elsif %w(member_of_group assigned_to_role).include?(field)
group = :field_assigned_to
elsif field_options[:type] == :date_past || field_options[:type] == :date
@@ -256,7 +256,7 @@ module QueriesHelper
link_to value, issue_path(item)
when :subject
link_to value, issue_path(item)
- when :parent
+ when :parent, :'issue.parent'
value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
when :description
item.description? ? content_tag('div', textilizable(item, :description), :class => "wiki") : ''
diff --git a/app/models/time_entry_query.rb b/app/models/time_entry_query.rb
index fab5a3448..b586350ca 100644
--- a/app/models/time_entry_query.rb
+++ b/app/models/time_entry_query.rb
@@ -31,6 +31,7 @@ class TimeEntryQuery < Query
QueryColumn.new(:activity, :sortable => "#{TimeEntryActivity.table_name}.position", :groupable => true),
QueryColumn.new(:issue, :sortable => "#{Issue.table_name}.id", :groupable => true),
QueryAssociationColumn.new(:issue, :tracker, :caption => :field_tracker, :sortable => "#{Tracker.table_name}.position"),
+ QueryAssociationColumn.new(:issue, :parent, :caption => :field_parent_issue, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc'),
QueryAssociationColumn.new(:issue, :status, :caption => :field_status, :sortable => "#{IssueStatus.table_name}.position"),
QueryAssociationColumn.new(:issue, :category, :caption => :field_category, :sortable => "#{IssueCategory.table_name}.name"),
QueryAssociationColumn.new(:issue, :fixed_version, :caption => :field_fixed_version, :sortable => Version.fields_for_order_statement),
@@ -62,6 +63,10 @@ class TimeEntryQuery < Query
:name => l("label_attribute_of_issue", :name => l(:field_tracker)),
:values => lambda {trackers.map {|t| [t.name, t.id.to_s]}})
add_available_filter(
+ "issue.parent_id",
+ :type => :tree,
+ :name => l("label_attribute_of_issue", :name => l(:field_parent_issue)))
+ add_available_filter(
"issue.status_id",
:type => :list,
:name => l("label_attribute_of_issue", :name => l(:field_status)),
@@ -205,6 +210,30 @@ class TimeEntryQuery < Query
end
end
+ def sql_for_issue_parent_id_field(field, operator, value)
+ case operator
+ when "="
+ # accepts a comma separated list of ids
+ parent_ids = value.first.to_s.scan(/\d+/).map(&:to_i).uniq
+ issue_ids = Issue.where(:parent_id => parent_ids).pluck(:id)
+ if issue_ids.present?
+ "#{TimeEntry.table_name}.issue_id IN (#{issue_ids.join(',')})"
+ else
+ "1=0"
+ end
+ when "~"
+ root_id, lft, rgt = Issue.where(:id => value.first.to_i).pick(:root_id, :lft, :rgt)
+ issue_ids = Issue.where("#{Issue.table_name}.root_id = ? AND #{Issue.table_name}.lft > ? AND #{Issue.table_name}.rgt < ?", root_id, lft, rgt).pluck(:id) if root_id && lft && rgt
+ if issue_ids.present?
+ "#{TimeEntry.table_name}.issue_id IN (#{issue_ids.join(',')})"
+ else
+ "1=0"
+ end
+ else
+ sql_for_field("parent_id", operator, value, Issue.table_name, "parent_id")
+ end
+ end
+
def sql_for_activity_id_field(field, operator, value)
ids = value.map(&:to_i).join(',')
table_name = Enumeration.table_name
diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb
index 2ccbf56f4..79e543253 100644
--- a/test/functional/timelog_controller_test.rb
+++ b/test/functional/timelog_controller_test.rb
@@ -1387,6 +1387,52 @@ class TimelogControllerTest < Redmine::ControllerTest
assert_select 'td.issue-category', :text => 'Printing'
end
+ def test_index_with_issue_parent_filter
+ issue1 = Issue.generate!(project_id: 'ecookbook', parent_id: 2)
+ entry1 = TimeEntry.generate!(issue: issue1, hours: 2.5)
+ issue2 = Issue.generate!(project_id: 'ecookbook', parent_id: 5)
+ entry2 = TimeEntry.generate!(issue: issue2, hours: 5.0)
+
+ get :index, params: {
+ project_id: 'ecookbook',
+ f: ['issue.parent_id'],
+ op: {'issue.parent_id' => '='},
+ v: {'issue.parent_id' => ['2,5']}
+ }
+ assert_response :success
+ assert_equal [entry1.id, entry2.id].sort, css_select('input[name="ids[]"]').map {|e| e.attr(:value).to_i}.sort
+ end
+
+ def test_index_with_issue_parent_column
+ issue = Issue.generate!(project_id: 'ecookbook', parent_id: 2)
+ entry = TimeEntry.generate!(issue: issue, hours: 2.5)
+
+ get :index, params: {
+ project_id: 'ecookbook',
+ c: %w(project spent_on issue comments hours issue.parent)
+ }
+
+ assert_response :success
+ assert_select 'td.issue-parent', text: "#{issue.parent.tracker} ##{issue.parent.id}"
+ end
+
+ def test_index_with_issue_parent_sort
+ issue1 = Issue.generate!(project_id: 'ecookbook', parent_id: 2)
+ entry1 = TimeEntry.generate!(issue: issue1, hours: 2.5)
+ issue2 = Issue.generate!(project_id: 'ecookbook', parent_id: 5)
+ entry2 = TimeEntry.generate!(issue: issue2, hours: 5.0)
+
+ get :index, :params => {
+ :c => ["hours", 'issue.parent'],
+ :sort => 'issue.parent'
+ }
+ assert_response :success
+
+ # Make sure that values are properly sorted
+ values = css_select("td.issue-parent").map(&:text).reject(&:blank?)
+ assert_equal ["#{issue1.parent.tracker} ##{issue1.parent.id}", "#{issue2.parent.tracker} ##{issue2.parent.id}"].sort, values.sort
+ end
+
def test_index_with_issue_fixed_version_column
issue = Issue.find(1)
issue.fixed_version = Version.find(3)
diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb
index 607679e83..55500ff13 100644
--- a/test/unit/query_test.rb
+++ b/test/unit/query_test.rb
@@ -504,6 +504,32 @@ class QueryTest < ActiveSupport::TestCase
find_issues_with_query(query)
end
+ def test_time_entry_operator_is_on_issue_parent_id_should_accept_comma_separated_values
+ issue1 = Issue.generate!(project_id: 'ecookbook', parent_id: 2)
+ entry1 = TimeEntry.generate!(issue: issue1)
+ issue2 = Issue.generate!(project_id: 'ecookbook', parent_id: 5)
+ entry2 = TimeEntry.generate!(issue: issue2)
+
+ query = TimeEntryQuery.new(:name => '_')
+ query.add_filter("issue.parent_id", '=', ['2,5'])
+ entries = TimeEntry.where(query.statement).to_a
+ assert_equal 2, entries.size
+ assert_equal [entry1.id, entry2.id].sort, entries.map(&:id).sort
+ end
+
+ def test_time_entry_contains_operator_is_on_issue_parent_id
+ issue1 = Issue.generate!(project_id: 'ecookbook', parent_id: 2)
+ entry1 = TimeEntry.generate!(issue: issue1)
+ issue2 = Issue.generate!(project_id: 'ecookbook', parent_id: issue1.id)
+ entry2 = TimeEntry.generate!(issue: issue2)
+
+ query = TimeEntryQuery.new(:name => '_')
+ query.add_filter("issue.parent_id", '~', ['2'])
+ entries = TimeEntry.where(query.statement).to_a
+ assert_equal 2, entries.size
+ assert_equal [entry1.id, entry2.id].sort, entries.map(&:id).sort
+ end
+
def test_date_filter_should_not_accept_non_date_values
query = IssueQuery.new(:name => '_')
query.add_filter('created_on', '=', ['a'])