Commit 8e4aea20 authored by jplang's avatar jplang

Allow issues grouping by creation, update and closing date (#13803).

Implemented for PostgreSQL only for now.

git-svn-id: https://svn.redmine.org/redmine/trunk@17724 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent 790d893e
...@@ -128,7 +128,7 @@ module QueriesHelper ...@@ -128,7 +128,7 @@ module QueriesHelper
items.each do |item| items.each do |item|
group_name = group_count = nil group_name = group_count = nil
if query.grouped? if query.grouped?
group = query.group_by_column.value(item) group = query.group_by_column.group_value(item)
if first || group != previous_group if first || group != previous_group
if group.blank? && group != false if group.blank? && group != false
group_name = "(#{l(:label_blank_value)})" group_name = "(#{l(:label_blank_value)})"
......
...@@ -30,7 +30,7 @@ class IssueQuery < Query ...@@ -30,7 +30,7 @@ class IssueQuery < Query
QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), TimestampQueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc', :groupable => true),
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date", :groupable => true), QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date", :groupable => true),
...@@ -41,8 +41,8 @@ class IssueQuery < Query ...@@ -41,8 +41,8 @@ class IssueQuery < Query
" WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)", " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
:default_order => 'desc'), :default_order => 'desc'),
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), TimestampQueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc', :groupable => true),
QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'), TimestampQueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc', :groupable => true),
QueryColumn.new(:last_updated_by, :sortable => lambda {User.fields_for_order_statement("last_journal_user")}), QueryColumn.new(:last_updated_by, :sortable => lambda {User.fields_for_order_statement("last_journal_user")}),
QueryColumn.new(:relations, :caption => :label_related_issues), QueryColumn.new(:relations, :caption => :label_related_issues),
QueryColumn.new(:attachments, :caption => :label_attachment_plural), QueryColumn.new(:attachments, :caption => :label_attachment_plural),
......
...@@ -71,11 +71,31 @@ class QueryColumn ...@@ -71,11 +71,31 @@ class QueryColumn
object.send name object.send name
end end
# Returns the group that object belongs to when grouping query results
def group_value(object)
value(object)
end
def css_classes def css_classes
name name
end end
end end
class TimestampQueryColumn < QueryColumn
def groupable
if @groupable
Redmine::Database.timestamp_to_date(sortable, User.current.time_zone)
end
end
def group_value(object)
if time = value(object)
User.current.time_to_date(time)
end
end
end
class QueryAssociationColumn < QueryColumn class QueryAssociationColumn < QueryColumn
def initialize(association, attribute, options={}) def initialize(association, attribute, options={})
......
...@@ -64,6 +64,19 @@ module Redmine ...@@ -64,6 +64,19 @@ module Redmine
end end
end end
# Returns a SQL statement to cast a timestamp column to a date given a time zone
# Returns nil if not implemented for the current database
def timestamp_to_date(column, time_zone)
if postgresql?
if time_zone
identifier = ActiveSupport::TimeZone.find_tzinfo(time_zone.name).identifier
"(#{column}::timestamptz AT TIME ZONE '#{identifier}')::date"
else
"#{column}::date"
end
end
end
# Resets database information # Resets database information
def reset def reset
@postgresql_unaccent = nil @postgresql_unaccent = nil
......
...@@ -303,7 +303,7 @@ module Redmine ...@@ -303,7 +303,7 @@ module Redmine
issue_list(issues) do |issue, level| issue_list(issues) do |issue, level|
if query.grouped? && if query.grouped? &&
(group = query.group_by_column.value(issue)) != previous_group (group = query.group_by_column.group_value(issue)) != previous_group
pdf.SetFontStyle('B',10) pdf.SetFontStyle('B',10)
group_label = group.blank? ? 'None' : group.to_s.dup group_label = group.blank? ? 'None' : group.to_s.dup
group_label << " (#{result_count_by_group[group]})" group_label << " (#{result_count_by_group[group]})"
......
...@@ -352,6 +352,32 @@ class IssuesControllerTest < Redmine::ControllerTest ...@@ -352,6 +352,32 @@ class IssuesControllerTest < Redmine::ControllerTest
end end
end end
def test_index_grouped_by_created_on
skip unless IssueQuery.new.groupable_columns.detect {|c| c.name == :created_on}
get :index, :params => {
:set_filter => 1,
:group_by => 'created_on'
}
assert_response :success
assert_select 'tr.group span.name', :text => '07/19/2006' do
assert_select '+ span.count', :text => '2'
end
end
def test_index_grouped_by_created_on_as_pdf
skip unless IssueQuery.new.groupable_columns.detect {|c| c.name == :created_on}
get :index, :params => {
:set_filter => 1,
:group_by => 'created_on',
:format => 'pdf'
}
assert_response :success
assert_equal 'application/pdf', response.content_type
end
def test_index_with_query_grouped_by_list_custom_field def test_index_with_query_grouped_by_list_custom_field
get :index, :params => { get :index, :params => {
:project_id => 1, :project_id => 1,
......
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