mirror of
https://github.com/redmine/redmine.git
synced 2025-11-09 23:06:05 +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_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
|
||||||
sort_update(@query.sortable_columns)
|
sort_update(@query.sortable_columns)
|
||||||
scope = time_entry_scope(:order => sort_clause).
|
scope = time_entry_scope(:order => sort_clause).
|
||||||
includes(:project, :user, :issue).
|
|
||||||
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
|
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|||||||
@@ -74,6 +74,26 @@ class QueryColumn
|
|||||||
end
|
end
|
||||||
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
|
class QueryCustomFieldColumn < QueryColumn
|
||||||
|
|
||||||
def initialize(custom_field, options={})
|
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(:user, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
|
||||||
QueryColumn.new(:activity, :sortable => "#{TimeEntryActivity.table_name}.position", :groupable => true),
|
QueryColumn.new(:activity, :sortable => "#{TimeEntryActivity.table_name}.position", :groupable => true),
|
||||||
QueryColumn.new(:issue, :sortable => "#{Issue.table_name}.id"),
|
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(:comments),
|
||||||
QueryColumn.new(:hours, :sortable => "#{TimeEntry.table_name}.hours", :totalable => true),
|
QueryColumn.new(:hours, :sortable => "#{TimeEntry.table_name}.hours", :totalable => true),
|
||||||
]
|
]
|
||||||
@@ -71,6 +73,14 @@ class TimeEntryQuery < Query
|
|||||||
end
|
end
|
||||||
|
|
||||||
add_available_filter("issue_id", :type => :tree, :label => :label_issue)
|
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",
|
add_available_filter("issue.fixed_version_id",
|
||||||
:type => :list,
|
:type => :list,
|
||||||
:name => l("label_attribute_of_issue", :name => l(:field_fixed_version)),
|
:name => l("label_attribute_of_issue", :name => l(:field_fixed_version)),
|
||||||
@@ -118,14 +128,16 @@ class TimeEntryQuery < Query
|
|||||||
end
|
end
|
||||||
|
|
||||||
def base_scope
|
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
|
end
|
||||||
|
|
||||||
def results_scope(options={})
|
def results_scope(options={})
|
||||||
order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
|
order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
|
||||||
|
|
||||||
TimeEntry.visible.
|
base_scope.
|
||||||
where(statement).
|
|
||||||
order(order_option).
|
order(order_option).
|
||||||
joins(joins_for_order_statement(order_option.join(','))).
|
joins(joins_for_order_statement(order_option.join(','))).
|
||||||
includes(:activity).
|
includes(:activity).
|
||||||
@@ -185,6 +197,14 @@ class TimeEntryQuery < Query
|
|||||||
end
|
end
|
||||||
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
|
# Accepts :from/:to params as shortcut filters
|
||||||
def build_from_params(params)
|
def build_from_params(params)
|
||||||
super
|
super
|
||||||
@@ -197,4 +217,20 @@ class TimeEntryQuery < Query
|
|||||||
end
|
end
|
||||||
self
|
self
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% 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>
|
<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 %>
|
<%= raw @query.inline_columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, entry)}</td>"}.join %>
|
||||||
<td class="buttons">
|
<td class="buttons">
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ module Redmine
|
|||||||
unless @criteria.empty?
|
unless @criteria.empty?
|
||||||
time_columns = %w(tyear tmonth tweek spent_on)
|
time_columns = %w(tyear tmonth tweek spent_on)
|
||||||
@hours = []
|
@hours = []
|
||||||
@scope.includes(:issue, :activity).
|
@scope.includes(:activity).
|
||||||
group(@criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).
|
group(@criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).
|
||||||
joins(@criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact).
|
joins(@criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact).
|
||||||
sum(:hours).each do |hash, hours|
|
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')}
|
assert_equal [t3, t1, t2].map(&:id).map(&:to_s), css_select('input[name="ids[]"]').map {|e| e.attr('value')}
|
||||||
end
|
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
|
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'})
|
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)
|
entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
|
||||||
|
|||||||
Reference in New Issue
Block a user