git-svn-id: http://svn.redmine.org/redmine/trunk@15738 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/3.4.0
@@ -45,7 +45,6 @@ class TimelogController < ApplicationController | |||
sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria) | |||
sort_update(@query.sortable_columns) | |||
scope = time_entry_scope(:order => sort_clause). | |||
includes(:project, :user, :issue). | |||
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority]) | |||
respond_to do |format| |
@@ -74,6 +74,26 @@ class QueryColumn | |||
end | |||
end | |||
class QueryAssociationColumn < QueryColumn | |||
def initialize(association, attribute, options={}) | |||
@association = association | |||
@attribute = attribute | |||
name_with_assoc = "#{association}.#{attribute}".to_sym | |||
super(name_with_assoc, options) | |||
end | |||
def value_object(object) | |||
if assoc = object.send(@association) | |||
assoc.send @attribute | |||
end | |||
end | |||
def css_classes | |||
@css_classes ||= "#{@association}-#{@attribute}" | |||
end | |||
end | |||
class QueryCustomFieldColumn < QueryColumn | |||
def initialize(custom_field, options={}) |
@@ -27,6 +27,8 @@ class TimeEntryQuery < Query | |||
QueryColumn.new(:user, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), | |||
QueryColumn.new(:activity, :sortable => "#{TimeEntryActivity.table_name}.position", :groupable => true), | |||
QueryColumn.new(:issue, :sortable => "#{Issue.table_name}.id"), | |||
QueryAssociationColumn.new(:issue, :tracker, :caption => :field_tracker, :sortable => "#{Tracker.table_name}.position"), | |||
QueryAssociationColumn.new(:issue, :status, :caption => :field_status, :sortable => "#{IssueStatus.table_name}.position"), | |||
QueryColumn.new(:comments), | |||
QueryColumn.new(:hours, :sortable => "#{TimeEntry.table_name}.hours", :totalable => true), | |||
] | |||
@@ -71,6 +73,14 @@ class TimeEntryQuery < Query | |||
end | |||
add_available_filter("issue_id", :type => :tree, :label => :label_issue) | |||
add_available_filter("issue.tracker_id", | |||
:type => :list, | |||
:name => l("label_attribute_of_issue", :name => l(:field_tracker)), | |||
:values => Tracker.sorted.map {|t| [t.name, t.id.to_s]}) | |||
add_available_filter("issue.status_id", | |||
:type => :list, | |||
:name => l("label_attribute_of_issue", :name => l(:field_status)), | |||
:values => IssueStatus.sorted.map {|s| [s.name, s.id.to_s]}) | |||
add_available_filter("issue.fixed_version_id", | |||
:type => :list, | |||
:name => l("label_attribute_of_issue", :name => l(:field_fixed_version)), | |||
@@ -118,14 +128,16 @@ class TimeEntryQuery < Query | |||
end | |||
def base_scope | |||
TimeEntry.visible.where(statement) | |||
TimeEntry.visible. | |||
joins(:project, :user). | |||
joins("LEFT OUTER JOIN issues ON issues.id = time_entries.issue_id"). | |||
where(statement) | |||
end | |||
def results_scope(options={}) | |||
order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) | |||
TimeEntry.visible. | |||
where(statement). | |||
base_scope. | |||
order(order_option). | |||
joins(joins_for_order_statement(order_option.join(','))). | |||
includes(:activity). | |||
@@ -185,6 +197,14 @@ class TimeEntryQuery < Query | |||
end | |||
end | |||
def sql_for_issue_tracker_id_field(field, operator, value) | |||
sql_for_field("tracker_id", operator, value, Issue.table_name, "tracker_id") | |||
end | |||
def sql_for_issue_status_id_field(field, operator, value) | |||
sql_for_field("status_id", operator, value, Issue.table_name, "status_id") | |||
end | |||
# Accepts :from/:to params as shortcut filters | |||
def build_from_params(params) | |||
super | |||
@@ -197,4 +217,20 @@ class TimeEntryQuery < Query | |||
end | |||
self | |||
end | |||
def joins_for_order_statement(order_options) | |||
joins = [super] | |||
if order_options | |||
if order_options.include?('issue_statuses') | |||
joins << "LEFT OUTER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id" | |||
end | |||
if order_options.include?('trackers') | |||
joins << "LEFT OUTER JOIN #{Tracker.table_name} ON #{Tracker.table_name}.id = #{Issue.table_name}.tracker_id" | |||
end | |||
end | |||
joins.compact! | |||
joins.any? ? joins.join(' ') : nil | |||
end | |||
end |
@@ -31,7 +31,7 @@ | |||
</td> | |||
</tr> | |||
<% end %> | |||
<tr class="time-entry <%= cycle("odd", "even") %> hascontextmenu"> | |||
<tr id="time-entry-<%= entry.id %>" class="time-entry <%= cycle("odd", "even") %> hascontextmenu"> | |||
<td class="checkbox hide-when-print"><%= check_box_tag("ids[]", entry.id, false, :id => nil) %></td> | |||
<%= raw @query.inline_columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, entry)}</td>"}.join %> | |||
<td class="buttons"> |
@@ -45,7 +45,7 @@ module Redmine | |||
unless @criteria.empty? | |||
time_columns = %w(tyear tmonth tweek spent_on) | |||
@hours = [] | |||
@scope.includes(:issue, :activity). | |||
@scope.includes(:activity). | |||
group(@criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns). | |||
joins(@criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact). | |||
sum(:hours).each do |hash, hours| |
@@ -797,6 +797,92 @@ class TimelogControllerTest < Redmine::ControllerTest | |||
assert_equal [t3, t1, t2].map(&:id).map(&:to_s), css_select('input[name="ids[]"]').map {|e| e.attr('value')} | |||
end | |||
def test_index_with_issue_status_filter | |||
Issue.where(:status_id => 4).update_all(:status_id => 2) | |||
issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :status_id => 4) | |||
entry = TimeEntry.generate!(:issue => issue, :hours => 4.5) | |||
get :index, :params => { | |||
:f => ['issue.status_id'], | |||
:op => {'issue.status_id' => '='}, | |||
:v => {'issue.status_id' => ['4']} | |||
} | |||
assert_response :success | |||
assert_equal [entry].map(&:id).map(&:to_s), css_select('input[name="ids[]"]').map {|e| e.attr('value')} | |||
end | |||
def test_index_with_issue_status_column | |||
issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :status_id => 4) | |||
entry = TimeEntry.generate!(:issue => issue) | |||
get :index, :params => { | |||
:c => %w(project spent_on issue comments hours issue.status) | |||
} | |||
assert_response :success | |||
assert_select 'td.issue-status', :text => issue.status.name | |||
end | |||
def test_index_with_issue_status_sort | |||
TimeEntry.delete_all | |||
TimeEntry.generate!(:issue => Issue.generate!(:status_id => 1)) | |||
TimeEntry.generate!(:issue => Issue.generate!(:status_id => 5)) | |||
TimeEntry.generate!(:issue => Issue.generate!(:status_id => 3)) | |||
TimeEntry.generate!(:project_id => 1) | |||
get :index, :params => { | |||
:c => ["hours", 'issue.status'], | |||
:sort => 'issue.status' | |||
} | |||
assert_response :success | |||
# Make sure that values are properly sorted | |||
values = css_select("td.issue-status").map(&:text).reject(&:blank?) | |||
assert_equal IssueStatus.where(:id => [1, 5, 3]).sorted.pluck(:name), values | |||
end | |||
def test_index_with_issue_tracker_filter | |||
Issue.where(:tracker_id => 2).update_all(:tracker_id => 1) | |||
issue = Issue.generate!(:project_id => 1, :tracker_id => 2) | |||
entry = TimeEntry.generate!(:issue => issue, :hours => 4.5) | |||
get :index, :params => { | |||
:f => ['issue.tracker_id'], | |||
:op => {'issue.tracker_id' => '='}, | |||
:v => {'issue.tracker_id' => ['2']} | |||
} | |||
assert_response :success | |||
assert_equal [entry].map(&:id).map(&:to_s), css_select('input[name="ids[]"]').map {|e| e.attr('value')} | |||
end | |||
def test_index_with_issue_tracker_column | |||
issue = Issue.generate!(:project_id => 1, :tracker_id => 2) | |||
entry = TimeEntry.generate!(:issue => issue) | |||
get :index, :params => { | |||
:c => %w(project spent_on issue comments hours issue.tracker) | |||
} | |||
assert_response :success | |||
assert_select 'td.issue-tracker', :text => issue.tracker.name | |||
end | |||
def test_index_with_issue_tracker_sort | |||
TimeEntry.delete_all | |||
TimeEntry.generate!(:issue => Issue.generate!(:tracker_id => 1)) | |||
TimeEntry.generate!(:issue => Issue.generate!(:tracker_id => 3)) | |||
TimeEntry.generate!(:issue => Issue.generate!(:tracker_id => 2)) | |||
TimeEntry.generate!(:project_id => 1) | |||
get :index, :params => { | |||
:c => ["hours", 'issue.tracker'], | |||
:sort => 'issue.tracker' | |||
} | |||
assert_response :success | |||
# Make sure that values are properly sorted | |||
values = css_select("td.issue-tracker").map(&:text).reject(&:blank?) | |||
assert_equal Tracker.where(:id => [1, 2, 3]).sorted.pluck(:name), values | |||
end | |||
def test_index_with_filter_on_issue_custom_field | |||
issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'}) | |||
entry = TimeEntry.generate!(:issue => issue, :hours => 2.5) |