Index: app/helpers/queries_helper.rb
===================================================================
--- app/helpers/queries_helper.rb (revision 16111)
+++ app/helpers/queries_helper.rb (working copy)
@@ -183,6 +183,8 @@
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
Index: app/models/issue.rb
===================================================================
--- app/models/issue.rb (revision 16111)
+++ app/models/issue.rb (working copy)
@@ -244,6 +244,7 @@
@spent_hours = nil
@total_spent_hours = nil
@total_estimated_hours = nil
+ @last_notes = nil
base_reload(*args)
end
@@ -1048,6 +1049,16 @@
@relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
end
+ def last_notes
+ if @last_notes
+ @last_notes
+ else
+ notes = self.journals.where.not(notes: '').to_a
+ notes.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, self.project)
+ notes.last.notes unless notes.empty?
+ end
+ end
+
# Preloads relations for a collection of issues
def self.load_relations(issues)
if issues.any?
@@ -1102,6 +1113,22 @@
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(: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}
+ note.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, issue.project)
+ 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)
Index: app/models/issue_query.rb
===================================================================
--- app/models/issue_query.rb (revision 16111)
+++ app/models/issue_query.rb (working copy)
@@ -44,7 +44,8 @@
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
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)
@@ -330,6 +331,9 @@
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)
Index: app/views/issues/_list.html.erb
===================================================================
--- app/views/issues/_list.html.erb (revision 16111)
+++ app/views/issues/_list.html.erb (working copy)
@@ -33,7 +33,12 @@
<% @query.block_columns.each do |column|
if (text = column_content(column, issue)) && text.present? -%>
- <%= text %> |
+
+ <% if query.block_columns.count > 1 %>
+ <%= column.caption %>
+ <% end %>
+ <%= text %>
+ |
<% end -%>
<% end -%>
Index: app/views/issues/index.html.erb
===================================================================
--- app/views/issues/index.html.erb (revision 16111)
+++ app/views/issues/index.html.erb (working copy)
@@ -37,6 +37,7 @@
+
<% if @issue_count > Setting.issues_export_limit.to_i %>
Index: app/views/timelog/_list.html.erb
===================================================================
--- app/views/timelog/_list.html.erb (revision 16111)
+++ app/views/timelog/_list.html.erb (working copy)
@@ -50,7 +50,12 @@
<% @query.block_columns.each do |column|
if (text = column_content(column, issue)) && text.present? -%>
- <%= text %> |
+
+ <% if query.block_columns.count > 1 %>
+ <%= column.caption %>
+ <% end %>
+ <%= text %>
+ |
<% end -%>
<% end -%>
Index: config/locales/en.yml
===================================================================
--- config/locales/en.yml (revision 16111)
+++ config/locales/en.yml (working copy)
@@ -1009,6 +1009,7 @@
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
Index: lib/redmine/export/pdf/issues_pdf_helper.rb
===================================================================
--- lib/redmine/export/pdf/issues_pdf_helper.rb (revision 16111)
+++ lib/redmine/export/pdf/issues_pdf_helper.rb (working copy)
@@ -266,8 +266,8 @@
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
@@ -327,7 +327,14 @@
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
pdf.SetFontStyle('B',10)
Index: public/stylesheets/application.css
===================================================================
--- public/stylesheets/application.css (revision 16111)
+++ public/stylesheets/application.css (working copy)
@@ -255,8 +255,8 @@
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%;}
Index: test/functional/issues_controller_test.rb
===================================================================
--- test/functional/issues_controller_test.rb (revision 16111)
+++ test/functional/issues_controller_test.rb (working copy)
@@ -959,6 +959,43 @@
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!
Index: test/unit/query_test.rb
===================================================================
--- test/unit/query_test.rb (revision 16111)
+++ test/unit/query_test.rb (working copy)
@@ -1107,10 +1107,10 @@
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
@@ -1127,6 +1127,13 @@
assert_not_nil issues.first.instance_variable_get("@spent_hours")
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}