Commit e98660ba authored by jplang's avatar jplang

Adds issue tracker and status columns and filters on spent time list (#23401).

git-svn-id: https://svn.redmine.org/redmine/trunk@15738 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent e9f52760
......@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment