Feature #1474 » rm1474-note_20_patch_against_trunk_r16111.patch
| app/helpers/queries_helper.rb (working copy) | ||
|---|---|---|
| 183 | 183 |
value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
|
| 184 | 184 |
when :description |
| 185 | 185 |
item.description? ? content_tag('div', textilizable(item, :description), :class => "wiki") : ''
|
| 186 |
when :last_notes |
|
| 187 |
item.last_notes.present? ? content_tag('div', textilizable(item, :last_notes), :class => "wiki") : ''
|
|
| 186 | 188 |
when :done_ratio |
| 187 | 189 |
progress_bar(value) |
| 188 | 190 |
when :relations |
| app/models/issue.rb (working copy) | ||
|---|---|---|
| 244 | 244 |
@spent_hours = nil |
| 245 | 245 |
@total_spent_hours = nil |
| 246 | 246 |
@total_estimated_hours = nil |
| 247 |
@last_notes = nil |
|
| 247 | 248 |
base_reload(*args) |
| 248 | 249 |
end |
| 249 | 250 | |
| ... | ... | |
| 1048 | 1049 |
@relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort) |
| 1049 | 1050 |
end |
| 1050 | 1051 | |
| 1052 |
def last_notes |
|
| 1053 |
if @last_notes |
|
| 1054 |
@last_notes |
|
| 1055 |
else |
|
| 1056 |
notes = self.journals.where.not(notes: '').to_a |
|
| 1057 |
notes.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, self.project) |
|
| 1058 |
notes.last.notes unless notes.empty? |
|
| 1059 |
end |
|
| 1060 |
end |
|
| 1061 | ||
| 1051 | 1062 |
# Preloads relations for a collection of issues |
| 1052 | 1063 |
def self.load_relations(issues) |
| 1053 | 1064 |
if issues.any? |
| ... | ... | |
| 1102 | 1113 |
end |
| 1103 | 1114 |
end |
| 1104 | 1115 | |
| 1116 |
# Preloads visible last notes for a collection of issues |
|
| 1117 |
def self.load_visible_last_notes(issues, user=User.current) |
|
| 1118 |
if issues.any? |
|
| 1119 |
issue_ids = issues.map(&:id) |
|
| 1120 | ||
| 1121 |
notes = Journal.joins(issue: :project).where.not(notes: ''). |
|
| 1122 |
where(:issues => {:id => issue_ids}).order("#{Journal.table_name}.id ASC").to_a
|
|
| 1123 | ||
| 1124 |
issues.each do |issue| |
|
| 1125 |
note = notes.select{|note| note.journalized_id == issue.id}
|
|
| 1126 |
note.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, issue.project) |
|
| 1127 |
issue.instance_variable_set "@last_notes", (note.empty? ? '' : note.last.notes) |
|
| 1128 |
end |
|
| 1129 |
end |
|
| 1130 |
end |
|
| 1131 | ||
| 1105 | 1132 |
# Finds an issue relation given its id. |
| 1106 | 1133 |
def find_relation(relation_id) |
| 1107 | 1134 |
IssueRelation.where("issue_to_id = ? OR issue_from_id = ?", id, id).find(relation_id)
|
| app/models/issue_query.rb (working copy) | ||
|---|---|---|
| 44 | 44 |
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
|
| 45 | 45 |
QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
|
| 46 | 46 |
QueryColumn.new(:relations, :caption => :label_related_issues), |
| 47 |
QueryColumn.new(:description, :inline => false) |
|
| 47 |
QueryColumn.new(:description, :inline => false), |
|
| 48 |
QueryColumn.new(:last_notes, :caption => :label_last_notes, :inline => false) |
|
| 48 | 49 |
] |
| 49 | 50 | |
| 50 | 51 |
def initialize(attributes=nil, *args) |
| ... | ... | |
| 330 | 331 |
if has_column?(:relations) |
| 331 | 332 |
Issue.load_visible_relations(issues) |
| 332 | 333 |
end |
| 334 |
if has_column?(:last_notes) |
|
| 335 |
Issue.load_visible_last_notes(issues) |
|
| 336 |
end |
|
| 333 | 337 |
issues |
| 334 | 338 |
rescue ::ActiveRecord::StatementInvalid => e |
| 335 | 339 |
raise StatementInvalid.new(e.message) |
| app/views/issues/_list.html.erb (working copy) | ||
|---|---|---|
| 33 | 33 |
<% @query.block_columns.each do |column| |
| 34 | 34 |
if (text = column_content(column, issue)) && text.present? -%> |
| 35 | 35 |
<tr class="<%= current_cycle %>"> |
| 36 |
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"><%= text %></td> |
|
| 36 |
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"> |
|
| 37 |
<% if query.block_columns.count > 1 %> |
|
| 38 |
<span><%= column.caption %></span> |
|
| 39 |
<% end %> |
|
| 40 |
<%= text %> |
|
| 41 |
</td> |
|
| 37 | 42 |
</tr> |
| 38 | 43 |
<% end -%> |
| 39 | 44 |
<% end -%> |
| app/views/issues/index.html.erb (working copy) | ||
|---|---|---|
| 37 | 37 |
</p> |
| 38 | 38 |
<p> |
| 39 | 39 |
<label><%= check_box_tag 'csv[description]', '1', @query.has_column?(:description) %> <%= l(:field_description) %></label> |
| 40 |
<label><%= check_box_tag 'csv[last_notes]', '1', @query.has_column?(:last_notes) %> <%= l(:label_last_notes) %></label> |
|
| 40 | 41 |
</p> |
| 41 | 42 |
<% if @issue_count > Setting.issues_export_limit.to_i %> |
| 42 | 43 |
<p class="icon icon-warning"> |
| app/views/timelog/_list.html.erb (working copy) | ||
|---|---|---|
| 50 | 50 |
<% @query.block_columns.each do |column| |
| 51 | 51 |
if (text = column_content(column, issue)) && text.present? -%> |
| 52 | 52 |
<tr class="<%= current_cycle %>"> |
| 53 |
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"><%= text %></td> |
|
| 53 |
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"> |
|
| 54 |
<% if query.block_columns.count > 1 %> |
|
| 55 |
<span><%= column.caption %></span> |
|
| 56 |
<% end %> |
|
| 57 |
<%= text %> |
|
| 58 |
</td> |
|
| 54 | 59 |
</tr> |
| 55 | 60 |
<% end -%> |
| 56 | 61 |
<% end -%> |
| config/locales/en.yml (working copy) | ||
|---|---|---|
| 1009 | 1009 |
label_font_default: Default font |
| 1010 | 1010 |
label_font_monospace: Monospaced font |
| 1011 | 1011 |
label_font_proportional: Proportional font |
| 1012 |
label_last_notes: Last notes |
|
| 1012 | 1013 | |
| 1013 | 1014 |
button_login: Login |
| 1014 | 1015 |
button_submit: Submit |
| lib/redmine/export/pdf/issues_pdf_helper.rb (working copy) | ||
|---|---|---|
| 266 | 266 |
table_width = col_width.inject(0, :+) |
| 267 | 267 |
end |
| 268 | 268 |
|
| 269 |
# use full width if the description is displayed
|
|
| 270 |
if table_width > 0 && query.has_column?(:description)
|
|
| 269 |
# use full width if the description or last_notes are displayed
|
|
| 270 |
if table_width > 0 && (query.has_column?(:description) || query.has_column?(:last_notes))
|
|
| 271 | 271 |
col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width}
|
| 272 | 272 |
table_width = col_width.inject(0, :+) |
| 273 | 273 |
end |
| ... | ... | |
| 327 | 327 |
pdf.RDMwriteHTMLCell(0, 5, 10, '', issue.description.to_s, issue.attachments, "LRBT") |
| 328 | 328 |
pdf.set_auto_page_break(false) |
| 329 | 329 |
end |
| 330 | ||
| 331 |
if query.has_column?(:last_notes) && issue.last_notes.present? |
|
| 332 |
pdf.set_x(10) |
|
| 333 |
pdf.set_auto_page_break(true, bottom_margin) |
|
| 334 |
pdf.RDMwriteHTMLCell(0, 5, 10, '', issue.last_notes.to_s, [], "LRBT") |
|
| 335 |
pdf.set_auto_page_break(false) |
|
| 330 | 336 |
end |
| 337 |
end |
|
| 331 | 338 |
|
| 332 | 339 |
if issues.size == Setting.issues_export_limit.to_i |
| 333 | 340 |
pdf.SetFontStyle('B',10)
|
| public/stylesheets/application.css (working copy) | ||
|---|---|---|
| 255 | 255 |
tr.issue td.relations { text-align: left; }
|
| 256 | 256 |
tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
|
| 257 | 257 |
tr.issue td.relations span {white-space: nowrap;}
|
| 258 |
table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;}
|
|
| 259 |
table.issues td.description pre {white-space:normal;}
|
|
| 258 |
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;}
|
|
| 259 |
table.issues td.description pre, table.issues td.last_notes pre {white-space:normal;}
|
|
| 260 | 260 | |
| 261 | 261 |
tr.issue.idnt td.subject {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%;}
|
| 262 | 262 |
tr.issue.idnt-1 td.subject {padding-left: 24px; background-position: 8px 50%;}
|
| test/functional/issues_controller_test.rb (working copy) | ||
|---|---|---|
| 959 | 959 |
assert_equal 'application/pdf', response.content_type |
| 960 | 960 |
end |
| 961 | 961 | |
| 962 |
def test_index_with_last_notes_column |
|
| 963 |
get :index, :set_filter => 1, :c => %w(subject last_notes) |
|
| 964 | ||
| 965 |
assert_response :success |
|
| 966 |
assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject |
|
| 967 | ||
| 968 |
assert_select 'td.last_notes[colspan="3"]', :text => 'Some notes with Redmine links: #2, r2.' |
|
| 969 |
assert_select 'td.last_notes[colspan="3"]', :text => 'A comment with inline image: and a reference to #1 and r2.' |
|
| 970 | ||
| 971 |
get :index, :set_filter => 1, :c => %w(subject last_notes), :format => 'pdf' |
|
| 972 |
assert_response :success |
|
| 973 |
assert_equal 'application/pdf', response.content_type |
|
| 974 |
end |
|
| 975 | ||
| 976 |
def test_index_with_last_notes_column_should_display_private_notes_with_permission_only |
|
| 977 |
journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1) |
|
| 978 |
@request.session[:user_id] = 2 |
|
| 979 | ||
| 980 |
get :index, :set_filter => 1, :c => %w(subject last_notes) |
|
| 981 |
assert_response :success |
|
| 982 |
assert_select 'td.last_notes[colspan="3"]', :text => 'Privates notes' |
|
| 983 | ||
| 984 |
Role.find(1).remove_permission! :view_private_notes |
|
| 985 | ||
| 986 |
get :index, :set_filter => 1, :c => %w(subject last_notes) |
|
| 987 |
assert_response :success |
|
| 988 |
assert_select 'td.last_notes[colspan="3"]', :text => 'A comment with inline image: and a reference to #1 and r2.' |
|
| 989 |
end |
|
| 990 | ||
| 991 |
def test_index_with_description_and_last_notes_columns_should_display_column_name |
|
| 992 |
get :index, :set_filter => 1, :c => %w(subject last_notes description) |
|
| 993 |
assert_response :success |
|
| 994 | ||
| 995 |
assert_select 'td.last_notes[colspan="3"] span', :text => 'Last notes' |
|
| 996 |
assert_select 'td.description[colspan="3"] span', :text => 'Description' |
|
| 997 |
end |
|
| 998 | ||
| 962 | 999 |
def test_index_with_parent_column |
| 963 | 1000 |
Issue.delete_all |
| 964 | 1001 |
parent = Issue.generate! |
| test/unit/query_test.rb (working copy) | ||
|---|---|---|
| 1107 | 1107 | |
| 1108 | 1108 |
def test_inline_and_block_columns |
| 1109 | 1109 |
q = IssueQuery.new |
| 1110 |
q.column_names = ['subject', 'description', 'tracker'] |
|
| 1110 |
q.column_names = ['subject', 'description', 'tracker', 'last_notes']
|
|
| 1111 | 1111 | |
| 1112 | 1112 |
assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name) |
| 1113 |
assert_equal [:description], q.block_columns.map(&:name) |
|
| 1113 |
assert_equal [:description, :last_notes], q.block_columns.map(&:name)
|
|
| 1114 | 1114 |
end |
| 1115 | 1115 | |
| 1116 | 1116 |
def test_custom_field_columns_should_be_inline |
| ... | ... | |
| 1127 | 1127 |
assert_not_nil issues.first.instance_variable_get("@spent_hours")
|
| 1128 | 1128 |
end |
| 1129 | 1129 | |
| 1130 |
def test_query_should_preload_last_notes |
|
| 1131 |
q = IssueQuery.new(:name => '_', :column_names => [:subject, :last_notes]) |
|
| 1132 |
assert q.has_column?(:last_notes) |
|
| 1133 |
issues = q.issues |
|
| 1134 |
assert_not_nil issues.first.instance_variable_get("@last_notes")
|
|
| 1135 |
end |
|
| 1136 | ||
| 1130 | 1137 |
def test_groupable_columns_should_include_custom_fields |
| 1131 | 1138 |
q = IssueQuery.new |
| 1132 | 1139 |
column = q.groupable_columns.detect {|c| c.name == :cf_1}
|