mirror of
https://github.com/redmine/redmine.git
synced 2025-11-09 14:56:01 +01:00
Adds issue tracker and status columns and filters on spent time list (#23401).
git-svn-id: http://svn.redmine.org/redmine/trunk@15738 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user