Patch by Marius BALTEANU. git-svn-id: http://svn.redmine.org/redmine/trunk@16367 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/3.4.0
@@ -185,6 +185,8 @@ module QueriesHelper | |||
value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : '' | |||
when :description | |||
item.description? ? content_tag('div', textilizable(item, :description), :class => "wiki") : '' | |||
when :last_notes | |||
item.last_notes.present? ? content_tag('div', textilizable(item, :last_notes), :class => "wiki") : '' | |||
when :done_ratio | |||
progress_bar(value) | |||
when :relations |
@@ -246,6 +246,7 @@ class Issue < ActiveRecord::Base | |||
@total_spent_hours = nil | |||
@total_estimated_hours = nil | |||
@last_updated_by = nil | |||
@last_notes = nil | |||
base_reload(*args) | |||
end | |||
@@ -1078,6 +1079,15 @@ class Issue < ActiveRecord::Base | |||
end | |||
end | |||
def last_notes | |||
if @last_notes | |||
@last_notes | |||
else | |||
notes = self.journals.visible.where.not(notes: '').to_a | |||
notes.last.notes unless notes.empty? | |||
end | |||
end | |||
# Preloads relations for a collection of issues | |||
def self.load_relations(issues) | |||
if issues.any? | |||
@@ -1158,6 +1168,22 @@ class Issue < ActiveRecord::Base | |||
end | |||
end | |||
# Preloads visible last notes for a collection of issues | |||
def self.load_visible_last_notes(issues, user=User.current) | |||
if issues.any? | |||
issue_ids = issues.map(&:id) | |||
notes = Journal.joins(issue: :project).where.not(notes: ''). | |||
where(Journal.visible_notes_condition(User.current, :skip_pre_condition => true)). | |||
where(:issues => {:id => issue_ids}).order("#{Journal.table_name}.id ASC").to_a | |||
issues.each do |issue| | |||
note = notes.select{|note| note.journalized_id == issue.id} | |||
issue.instance_variable_set "@last_notes", (note.empty? ? '' : note.last.notes) | |||
end | |||
end | |||
end | |||
# Finds an issue relation given its id. | |||
def find_relation(relation_id) | |||
IssueRelation.where("issue_to_id = ? OR issue_from_id = ?", id, id).find(relation_id) |
@@ -45,7 +45,8 @@ class IssueQuery < Query | |||
QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'), | |||
QueryColumn.new(:last_updated_by, :sortable => lambda {User.fields_for_order_statement("last_journal_user")}), | |||
QueryColumn.new(:relations, :caption => :label_related_issues), | |||
QueryColumn.new(:description, :inline => false) | |||
QueryColumn.new(:description, :inline => false), | |||
QueryColumn.new(:last_notes, :caption => :label_last_notes, :inline => false) | |||
] | |||
def initialize(attributes=nil, *args) | |||
@@ -305,6 +306,9 @@ class IssueQuery < Query | |||
if has_column?(:relations) | |||
Issue.load_visible_relations(issues) | |||
end | |||
if has_column?(:last_notes) | |||
Issue.load_visible_last_notes(issues) | |||
end | |||
issues | |||
rescue ::ActiveRecord::StatementInvalid => e | |||
raise StatementInvalid.new(e.message) |
@@ -33,7 +33,12 @@ | |||
<% @query.block_columns.each do |column| | |||
if (text = column_content(column, issue)) && text.present? -%> | |||
<tr class="<%= current_cycle %>"> | |||
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"><%= text %></td> | |||
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"> | |||
<% if query.block_columns.count > 1 %> | |||
<span><%= column.caption %></span> | |||
<% end %> | |||
<%= text %> | |||
</td> | |||
</tr> | |||
<% end -%> | |||
<% end -%> |
@@ -37,6 +37,7 @@ | |||
</p> | |||
<p> | |||
<label><%= check_box_tag 'c[]', 'description', @query.has_column?(:description) %> <%= l(:field_description) %></label> | |||
<label><%= check_box_tag 'c[]', 'last_notes', @query.has_column?(:last_notes) %> <%= l(:label_last_notes) %></label> | |||
</p> | |||
<% if @issue_count > Setting.issues_export_limit.to_i %> | |||
<p class="icon icon-warning"> |
@@ -50,7 +50,12 @@ | |||
<% @query.block_columns.each do |column| | |||
if (text = column_content(column, issue)) && text.present? -%> | |||
<tr class="<%= current_cycle %>"> | |||
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"><%= text %></td> | |||
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"> | |||
<% if query.block_columns.count > 1 %> | |||
<span><%= column.caption %></span> | |||
<% end %> | |||
<%= text %> | |||
</td> | |||
</tr> | |||
<% end -%> | |||
<% end -%> |
@@ -1014,6 +1014,7 @@ en: | |||
label_font_default: Default font | |||
label_font_monospace: Monospaced font | |||
label_font_proportional: Proportional font | |||
label_last_notes: Last notes | |||
button_login: Login | |||
button_submit: Submit |
@@ -278,8 +278,8 @@ module Redmine | |||
table_width = col_width.inject(0, :+) | |||
end | |||
# use full width if the description is displayed | |||
if table_width > 0 && query.has_column?(:description) | |||
# use full width if the description or last_notes are displayed | |||
if table_width > 0 && (query.has_column?(:description) || query.has_column?(:last_notes)) | |||
col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width} | |||
table_width = col_width.inject(0, :+) | |||
end | |||
@@ -339,6 +339,13 @@ module Redmine | |||
pdf.RDMwriteHTMLCell(0, 5, 10, '', issue.description.to_s, issue.attachments, "LRBT") | |||
pdf.set_auto_page_break(false) | |||
end | |||
if query.has_column?(:last_notes) && issue.last_notes.present? | |||
pdf.set_x(10) | |||
pdf.set_auto_page_break(true, bottom_margin) | |||
pdf.RDMwriteHTMLCell(0, 5, 10, '', issue.last_notes.to_s, [], "LRBT") | |||
pdf.set_auto_page_break(false) | |||
end | |||
end | |||
if issues.size == Setting.issues_export_limit.to_i |
@@ -258,8 +258,8 @@ tr.issue td.subject, tr.issue td.category, td.assigned_to, td.last_updated_by, t | |||
tr.issue td.relations { text-align: left; } | |||
tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;} | |||
tr.issue td.relations span {white-space: nowrap;} | |||
table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;} | |||
table.issues td.description pre {white-space:normal;} | |||
table.issues td.description, table.issues td.last_notes {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;} | |||
table.issues td.description pre, table.issues td.last_notes pre {white-space:normal;} | |||
tr.issue.idnt td.subject {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%;} | |||
tr.issue.idnt-1 td.subject {padding-left: 24px; background-position: 8px 50%;} |
@@ -971,6 +971,43 @@ class IssuesControllerTest < Redmine::ControllerTest | |||
assert_equal 'application/pdf', response.content_type | |||
end | |||
def test_index_with_last_notes_column | |||
get :index, :set_filter => 1, :c => %w(subject last_notes) | |||
assert_response :success | |||
assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject | |||
assert_select 'td.last_notes[colspan="3"]', :text => 'Some notes with Redmine links: #2, r2.' | |||
assert_select 'td.last_notes[colspan="3"]', :text => 'A comment with inline image: and a reference to #1 and r2.' | |||
get :index, :set_filter => 1, :c => %w(subject last_notes), :format => 'pdf' | |||
assert_response :success | |||
assert_equal 'application/pdf', response.content_type | |||
end | |||
def test_index_with_last_notes_column_should_display_private_notes_with_permission_only | |||
journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1) | |||
@request.session[:user_id] = 2 | |||
get :index, :set_filter => 1, :c => %w(subject last_notes) | |||
assert_response :success | |||
assert_select 'td.last_notes[colspan="3"]', :text => 'Privates notes' | |||
Role.find(1).remove_permission! :view_private_notes | |||
get :index, :set_filter => 1, :c => %w(subject last_notes) | |||
assert_response :success | |||
assert_select 'td.last_notes[colspan="3"]', :text => 'A comment with inline image: and a reference to #1 and r2.' | |||
end | |||
def test_index_with_description_and_last_notes_columns_should_display_column_name | |||
get :index, :set_filter => 1, :c => %w(subject last_notes description) | |||
assert_response :success | |||
assert_select 'td.last_notes[colspan="3"] span', :text => 'Last notes' | |||
assert_select 'td.description[colspan="3"] span', :text => 'Description' | |||
end | |||
def test_index_with_parent_column | |||
Issue.delete_all | |||
parent = Issue.generate! |
@@ -1302,10 +1302,10 @@ class QueryTest < ActiveSupport::TestCase | |||
def test_inline_and_block_columns | |||
q = IssueQuery.new | |||
q.column_names = ['subject', 'description', 'tracker'] | |||
q.column_names = ['subject', 'description', 'tracker', 'last_notes'] | |||
assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name) | |||
assert_equal [:description], q.block_columns.map(&:name) | |||
assert_equal [:description, :last_notes], q.block_columns.map(&:name) | |||
end | |||
def test_custom_field_columns_should_be_inline | |||
@@ -1335,6 +1335,13 @@ class QueryTest < ActiveSupport::TestCase | |||
end | |||
end | |||
def test_query_should_preload_last_notes | |||
q = IssueQuery.new(:name => '_', :column_names => [:subject, :last_notes]) | |||
assert q.has_column?(:last_notes) | |||
issues = q.issues | |||
assert_not_nil issues.first.instance_variable_get("@last_notes") | |||
end | |||
def test_groupable_columns_should_include_custom_fields | |||
q = IssueQuery.new | |||
column = q.groupable_columns.detect {|c| c.name == :cf_1} |