##// END OF EJS Templates
Enable updating private_notes property on journal edit form (#22575)....
Jean-Philippe Lang -
r15238:7918285ac2e6
parent child
Show More
@@ -1,110 +1,113
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class JournalsController < ApplicationController
19 19 before_filter :find_journal, :only => [:edit, :update, :diff]
20 20 before_filter :find_issue, :only => [:new]
21 21 before_filter :find_optional_project, :only => [:index]
22 22 before_filter :authorize, :only => [:new, :edit, :update, :diff]
23 23 accept_rss_auth :index
24 24 menu_item :issues
25 25
26 26 helper :issues
27 27 helper :custom_fields
28 28 helper :queries
29 29 include QueriesHelper
30 30 helper :sort
31 31 include SortHelper
32 32
33 33 def index
34 34 retrieve_query
35 35 sort_init 'id', 'desc'
36 36 sort_update(@query.sortable_columns)
37 37 if @query.valid?
38 38 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
39 39 :limit => 25)
40 40 end
41 41 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
42 42 render :layout => false, :content_type => 'application/atom+xml'
43 43 rescue ActiveRecord::RecordNotFound
44 44 render_404
45 45 end
46 46
47 47 def diff
48 48 @issue = @journal.issue
49 49 if params[:detail_id].present?
50 50 @detail = @journal.details.find_by_id(params[:detail_id])
51 51 else
52 52 @detail = @journal.details.detect {|d| d.property == 'attr' && d.prop_key == 'description'}
53 53 end
54 54 unless @issue && @detail
55 55 render_404
56 56 return false
57 57 end
58 58 if @detail.property == 'cf'
59 59 unless @detail.custom_field && @detail.custom_field.visible_by?(@issue.project, User.current)
60 60 raise ::Unauthorized
61 61 end
62 62 end
63 63 @diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value)
64 64 end
65 65
66 66 def new
67 67 @journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
68 68 if @journal
69 69 user = @journal.user
70 70 text = @journal.notes
71 71 else
72 72 user = @issue.author
73 73 text = @issue.description
74 74 end
75 75 # Replaces pre blocks with [...]
76 76 text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]')
77 77 @content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
78 78 @content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
79 79 rescue ActiveRecord::RecordNotFound
80 80 render_404
81 81 end
82 82
83 83 def edit
84 84 (render_403; return false) unless @journal.editable_by?(User.current)
85 85 respond_to do |format|
86 86 # TODO: implement non-JS journal update
87 87 format.js
88 88 end
89 89 end
90 90
91 91 def update
92 92 (render_403; return false) unless @journal.editable_by?(User.current)
93 @journal.update_attributes(:notes => params[:notes]) if params[:notes]
93 @journal.notes = params[:notes] if params[:notes]
94 @journal.private_notes = params[:private_notes].present?
95 (render_403; return false) if @journal.private_notes_changed? && User.current.allowed_to?(:set_notes_private, @journal.issue.project) == false
96 @journal.save if @journal.changed?
94 97 @journal.destroy if @journal.details.empty? && @journal.notes.blank?
95 98 call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
96 99 respond_to do |format|
97 100 format.html { redirect_to issue_path(@journal.journalized) }
98 101 format.js
99 102 end
100 103 end
101 104
102 105 private
103 106
104 107 def find_journal
105 108 @journal = Journal.visible.find(params[:id])
106 109 @project = @journal.journalized.project
107 110 rescue ActiveRecord::RecordNotFound
108 111 render_404
109 112 end
110 113 end
@@ -1,61 +1,67
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module JournalsHelper
21 21
22 22 # Returns the attachments of a journal that are displayed as thumbnails
23 23 def journal_thumbnail_attachments(journal)
24 24 ids = journal.details.select {|d| d.property == 'attachment' && d.value.present?}.map(&:prop_key)
25 25 ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?) : []
26 26 end
27 27
28 28 def render_notes(issue, journal, options={})
29 29 content = ''
30 30 editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project)))
31 31 links = []
32 32 if !journal.notes.blank?
33 33 links << link_to(l(:button_quote),
34 34 quoted_issue_path(issue, :journal_id => journal),
35 35 :remote => true,
36 36 :method => 'post',
37 37 :title => l(:button_quote),
38 38 :class => 'icon-only icon-comment'
39 39 ) if options[:reply_links]
40 40 links << link_to(l(:button_edit),
41 41 edit_journal_path(journal),
42 42 :remote => true,
43 43 :method => 'get',
44 44 :title => l(:button_edit),
45 45 :class => 'icon-only icon-edit'
46 46 ) if editable
47 47 links << link_to(l(:button_delete),
48 48 journal_path(journal, :notes => ""),
49 49 :remote => true,
50 50 :method => 'put', :data => {:confirm => l(:text_are_you_sure)},
51 51 :title => l(:button_delete),
52 52 :class => 'icon-only icon-del'
53 53 ) if editable
54 54 end
55 55 content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
56 56 content << textilizable(journal, :notes)
57 57 css_classes = "wiki"
58 58 css_classes << " editable" if editable
59 59 content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
60 60 end
61
62 def render_private_notes(journal)
63 content = journal.private_notes? ? l(:field_is_private) : ''
64 css_classes = journal.private_notes? ? 'private' : ''
65 content_tag('span', content.html_safe, :id => "journal-#{journal.id}-private_notes", :class => css_classes)
66 end
61 67 end
@@ -1,30 +1,30
1 1 <% reply_links = issue.notes_addable? -%>
2 2 <% for journal in journals %>
3 3 <div id="change-<%= journal.id %>" class="<%= journal.css_classes %>">
4 4 <div id="note-<%= journal.indice %>">
5 5 <h4><a href="#note-<%= journal.indice %>" class="journal-link">#<%= journal.indice %></a>
6 6 <%= avatar(journal.user, :size => "24") %>
7 7 <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %>
8 <%= content_tag('span', l(:field_is_private), :class => 'private') if journal.private_notes? %></h4>
8 <%= render_private_notes(journal) %></h4>
9 9
10 10 <% if journal.details.any? %>
11 11 <ul class="details">
12 12 <% details_to_strings(journal.visible_details).each do |string| %>
13 13 <li><%= string %></li>
14 14 <% end %>
15 15 </ul>
16 16 <% if Setting.thumbnails_enabled? && (thumbnail_attachments = journal_thumbnail_attachments(journal)).any? %>
17 17 <div class="thumbnails">
18 18 <% thumbnail_attachments.each do |attachment| %>
19 19 <div><%= thumbnail_tag(attachment) %></div>
20 20 <% end %>
21 21 </div>
22 22 <% end %>
23 23 <% end %>
24 24 <%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %>
25 25 </div>
26 26 </div>
27 27 <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>
28 28 <% end %>
29 29
30 30 <% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %>
@@ -1,19 +1,22
1 1 <%= form_tag(journal_path(@journal),
2 2 :remote => true,
3 3 :method => 'put',
4 4 :id => "journal-#{@journal.id}-form") do %>
5 5 <%= label_tag "notes", l(:description_notes), :class => "hidden-for-sighted" %>
6 6 <%= text_area_tag :notes, @journal.notes,
7 7 :id => "journal_#{@journal.id}_notes",
8 8 :class => 'wiki-edit',
9 9 :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %>
10 <% if @journal.issue.safe_attribute? 'private_notes' %>
11 <%= check_box_tag 'private_notes', '1', @journal.private_notes, :id => "journal_#{@journal.id}_private_notes" %> <label for="journal_<%= @journal.id %>_private_notes"><%= l(:field_private_notes) %></label>
12 <% end %>
10 13 <%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %>
11 14 <p><%= submit_tag l(:button_save) %>
12 15 <%= preview_link preview_edit_issue_path(:project_id => @project, :id => @journal.issue),
13 16 "journal-#{@journal.id}-form",
14 17 "journal_#{@journal.id}_preview" %> |
15 18 <%= link_to l(:button_cancel), '#', :onclick => "$('#journal-#{@journal.id}-form').remove(); $('#journal-#{@journal.id}-notes').show(); return false;" %></p>
16 19
17 20 <div id="journal_<%= @journal.id %>_preview" class="wiki"></div>
18 21 <% end %>
19 22 <%= wikitoolbar_for "journal_#{@journal.id}_notes" %>
@@ -1,9 +1,11
1 1 <% if @journal.frozen? %>
2 2 $("#change-<%= @journal.id %>").remove();
3 3 <% else %>
4 $("#change-<%= @journal.id %>").attr('class', '<%= @journal.css_classes %>');
4 5 $("#journal-<%= @journal.id %>-notes").replaceWith('<%= escape_javascript(render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit'))) %>');
6 $("#journal-<%= @journal.id %>-private_notes").replaceWith('<%= escape_javascript(render_private_notes(@journal)) %>');
5 7 $("#journal-<%= @journal.id %>-notes").show();
6 8 $("#journal-<%= @journal.id %>-form").remove();
7 9 <% end %>
8 10
9 11 <%= call_hook(:view_journals_update_js_bottom, { :journal => @journal }) %>
@@ -1,221 +1,254
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class JournalsControllerTest < ActionController::TestCase
21 21 fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules,
22 22 :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects, :projects_trackers
23 23
24 24 def setup
25 25 User.current = nil
26 26 end
27 27
28 28 def test_index
29 29 get :index, :project_id => 1
30 30 assert_response :success
31 31 assert_not_nil assigns(:journals)
32 32 assert_equal 'application/atom+xml', @response.content_type
33 33 end
34 34
35 35 def test_index_with_invalid_query_id
36 36 get :index, :project_id => 1, :query_id => 999
37 37 assert_response 404
38 38 end
39 39
40 40 def test_index_should_return_privates_notes_with_permission_only
41 41 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
42 42 @request.session[:user_id] = 2
43 43
44 44 get :index, :project_id => 1
45 45 assert_response :success
46 46 assert_include journal, assigns(:journals)
47 47
48 48 Role.find(1).remove_permission! :view_private_notes
49 49 get :index, :project_id => 1
50 50 assert_response :success
51 51 assert_not_include journal, assigns(:journals)
52 52 end
53 53
54 54 def test_index_should_show_visible_custom_fields_only
55 55 Issue.destroy_all
56 56 field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all}
57 57 @fields = []
58 58 @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true)))
59 59 @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2])))
60 60 @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3])))
61 61 @issue = Issue.generate!(
62 62 :author_id => 1,
63 63 :project_id => 1,
64 64 :tracker_id => 1,
65 65 :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'}
66 66 )
67 67 @issue.init_journal(User.find(1))
68 68 @issue.update_attribute :custom_field_values, {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'}
69 69
70 70
71 71 user_with_role_on_other_project = User.generate!
72 72 User.add_to_project(user_with_role_on_other_project, Project.find(2), Role.find(3))
73 73 users_to_test = {
74 74 User.find(1) => [@field1, @field2, @field3],
75 75 User.find(3) => [@field1, @field2],
76 76 user_with_role_on_other_project => [@field1], # should see field1 only on Project 1
77 77 User.generate! => [@field1],
78 78 User.anonymous => [@field1]
79 79 }
80 80
81 81 users_to_test.each do |user, visible_fields|
82 82 get :index, :format => 'atom', :key => user.rss_key
83 83 @fields.each_with_index do |field, i|
84 84 if visible_fields.include?(field)
85 85 assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 1 }, "User #{user.id} was not able to view #{field.name} in API"
86 86 else
87 87 assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 0 }, "User #{user.id} was able to view #{field.name} in API"
88 88 end
89 89 end
90 90 end
91 91
92 92 end
93 93
94 94 def test_diff_for_description_change
95 95 get :diff, :id => 3, :detail_id => 4
96 96 assert_response :success
97 97 assert_template 'diff'
98 98
99 99 assert_select 'span.diff_out', :text => /removed/
100 100 assert_select 'span.diff_in', :text => /added/
101 101 end
102 102
103 103 def test_diff_for_custom_field
104 104 field = IssueCustomField.create!(:name => "Long field", :field_format => 'text')
105 105 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1)
106 106 detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id,
107 107 :old_value => 'Foo', :value => 'Bar')
108 108
109 109 get :diff, :id => journal.id, :detail_id => detail.id
110 110 assert_response :success
111 111 assert_template 'diff'
112 112
113 113 assert_select 'span.diff_out', :text => /Foo/
114 114 assert_select 'span.diff_in', :text => /Bar/
115 115 end
116 116
117 117 def test_diff_for_custom_field_should_be_denied_if_custom_field_is_not_visible
118 118 field = IssueCustomField.create!(:name => "Long field", :field_format => 'text', :visible => false, :role_ids => [1])
119 119 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1)
120 120 detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id,
121 121 :old_value => 'Foo', :value => 'Bar')
122 122
123 123 get :diff, :id => journal.id, :detail_id => detail.id
124 124 assert_response 302
125 125 end
126 126
127 127 def test_diff_should_default_to_description_diff
128 128 get :diff, :id => 3
129 129 assert_response :success
130 130 assert_template 'diff'
131 131
132 132 assert_select 'span.diff_out', :text => /removed/
133 133 assert_select 'span.diff_in', :text => /added/
134 134 end
135 135
136 136 def test_reply_to_issue
137 137 @request.session[:user_id] = 2
138 138 xhr :get, :new, :id => 6
139 139 assert_response :success
140 140 assert_template 'new'
141 141 assert_equal 'text/javascript', response.content_type
142 142 assert_include '> This is an issue', response.body
143 143 end
144 144
145 145 def test_reply_to_issue_without_permission
146 146 @request.session[:user_id] = 7
147 147 xhr :get, :new, :id => 6
148 148 assert_response 403
149 149 end
150 150
151 151 def test_reply_to_note
152 152 @request.session[:user_id] = 2
153 153 xhr :get, :new, :id => 6, :journal_id => 4
154 154 assert_response :success
155 155 assert_template 'new'
156 156 assert_equal 'text/javascript', response.content_type
157 157 assert_include '> A comment with a private version', response.body
158 158 end
159 159
160 160 def test_reply_to_private_note_should_fail_without_permission
161 161 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true)
162 162 @request.session[:user_id] = 2
163 163
164 164 xhr :get, :new, :id => 2, :journal_id => journal.id
165 165 assert_response :success
166 166 assert_template 'new'
167 167 assert_equal 'text/javascript', response.content_type
168 168 assert_include '> Privates notes', response.body
169 169
170 170 Role.find(1).remove_permission! :view_private_notes
171 171 xhr :get, :new, :id => 2, :journal_id => journal.id
172 172 assert_response 404
173 173 end
174 174
175 175 def test_edit_xhr
176 176 @request.session[:user_id] = 1
177 177 xhr :get, :edit, :id => 2
178 178 assert_response :success
179 179 assert_template 'edit'
180 180 assert_equal 'text/javascript', response.content_type
181 181 assert_include 'textarea', response.body
182 182 end
183 183
184 184 def test_edit_private_note_should_fail_without_permission
185 185 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true)
186 186 @request.session[:user_id] = 2
187 187 Role.find(1).add_permission! :edit_issue_notes
188 188
189 189 xhr :get, :edit, :id => journal.id
190 190 assert_response :success
191 191 assert_template 'edit'
192 192 assert_equal 'text/javascript', response.content_type
193 193 assert_include 'textarea', response.body
194 194
195 195 Role.find(1).remove_permission! :view_private_notes
196 196 xhr :get, :edit, :id => journal.id
197 197 assert_response 404
198 198 end
199 199
200 200 def test_update_xhr
201 201 @request.session[:user_id] = 1
202 202 xhr :post, :update, :id => 2, :notes => 'Updated notes'
203 203 assert_response :success
204 204 assert_template 'update'
205 205 assert_equal 'text/javascript', response.content_type
206 206 assert_equal 'Updated notes', Journal.find(2).notes
207 207 assert_include 'journal-2-notes', response.body
208 208 end
209 209
210 def test_update_xhr_with_private_notes_checked
211 @request.session[:user_id] = 1
212 xhr :post, :update, :id => 2, :private_notes => '1'
213 assert_response :success
214 assert_template 'update'
215 assert_equal 'text/javascript', response.content_type
216 assert_equal true, Journal.find(2).private_notes
217 assert_include 'change-2', response.body
218 assert_include 'journal-2-private_notes', response.body
219 end
220
221 def test_update_xhr_with_private_notes_unchecked
222 Journal.find(2).update_attributes(:private_notes => true)
223 @request.session[:user_id] = 1
224 xhr :post, :update, :id => 2
225 assert_response :success
226 assert_template 'update'
227 assert_equal 'text/javascript', response.content_type
228 assert_equal false, Journal.find(2).private_notes
229 assert_include 'change-2', response.body
230 assert_include 'journal-2-private_notes', response.body
231 end
232
233 def test_update_xhr_with_private_notes_changes_and_without_set_private_notes_permission
234 @request.session[:user_id] = 2
235 Role.find(1).add_permission! :edit_issue_notes
236 Role.find(1).add_permission! :view_private_notes
237 Role.find(1).remove_permission! :set_notes_private
238
239 xhr :post, :update, :id => 2, :private_notes => '1'
240 assert_response 403
241 end
242
210 243 def test_update_xhr_with_empty_notes_should_delete_the_journal
211 244 @request.session[:user_id] = 1
212 245 assert_difference 'Journal.count', -1 do
213 246 xhr :post, :update, :id => 2, :notes => ''
214 247 assert_response :success
215 248 assert_template 'update'
216 249 assert_equal 'text/javascript', response.content_type
217 250 end
218 251 assert_nil Journal.find_by_id(2)
219 252 assert_include 'change-2', response.body
220 253 end
221 254 end
General Comments 0
You need to be logged in to leave comments. Login now