Feature #13919 » 0001-Allow-users-to-be-mentioned-using.patch
| app/controllers/auto_completes_controller.rb | ||
|---|---|---|
| 60 | 60 |
render json: format_wiki_pages_json(wiki_pages) |
| 61 | 61 |
end |
| 62 | 62 | |
| 63 |
def users |
|
| 64 |
users = [] |
|
| 65 |
q = (params[:q] || params[:term]).to_s.strip |
|
| 66 |
scope = nil |
|
| 67 |
if params[:q].blank? && @project.present? |
|
| 68 |
scope = @project.users |
|
| 69 |
else |
|
| 70 |
scope = User.all.limit(10) |
|
| 71 |
end |
|
| 72 |
users = scope.active.visible.sorted.like(params[:q]).to_a |
|
| 73 |
render :json => format_users_json(users) |
|
| 74 |
end |
|
| 75 | ||
| 63 | 76 |
private |
| 64 | 77 | |
| 65 | 78 |
def find_project |
| ... | ... | |
| 89 | 102 |
} |
| 90 | 103 |
end |
| 91 | 104 |
end |
| 105 | ||
| 106 |
def format_users_json(users) |
|
| 107 |
users.map {|user| {
|
|
| 108 |
'firstname' => user.firstname, |
|
| 109 |
'lastname' => user.lastname, |
|
| 110 |
'name' => user.name, |
|
| 111 |
'login' => user.login |
|
| 112 |
} |
|
| 113 |
} |
|
| 114 |
end |
|
| 92 | 115 |
end |
| app/helpers/application_helper.rb | ||
|---|---|---|
| 1822 | 1822 |
def autocomplete_data_sources(project) |
| 1823 | 1823 |
{
|
| 1824 | 1824 |
issues: auto_complete_issues_path(:project_id => project, :q => ''), |
| 1825 |
wiki_pages: auto_complete_wiki_pages_path(:project_id => project, :q => '') |
|
| 1825 |
wiki_pages: auto_complete_wiki_pages_path(:project_id => project, :q => ''), |
|
| 1826 |
users: auto_complete_users_path(:project_id => @project, :q => '') |
|
| 1826 | 1827 |
} |
| 1827 | 1828 |
end |
| 1828 | 1829 | |
| app/models/issue.rb | ||
|---|---|---|
| 54 | 54 |
acts_as_activity_provider :scope => proc {preload(:project, :author, :tracker, :status)},
|
| 55 | 55 |
:author_key => :author_id |
| 56 | 56 | |
| 57 |
acts_as_mentionable :attributes => ['description'] |
|
| 58 | ||
| 57 | 59 |
DONE_RATIO_OPTIONS = %w(issue_field issue_status) |
| 58 | 60 | |
| 59 | 61 |
attr_reader :transition_warning |
| app/models/journal.rb | ||
|---|---|---|
| 58 | 58 |
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')").distinct
|
| 59 | 59 |
end |
| 60 | 60 |
) |
| 61 |
acts_as_mentionable :attributes => ['notes'] |
|
| 61 | 62 |
before_create :split_private_notes |
| 62 | 63 |
after_create_commit :send_notification |
| 63 | 64 | |
| ... | ... | |
| 172 | 173 | |
| 173 | 174 |
def notified_watchers |
| 174 | 175 |
notified = journalized.notified_watchers |
| 175 |
if private_notes? |
|
| 176 |
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
|
|
| 177 |
end |
|
| 176 |
notified = select_journal_visible_user(notified) |
|
| 177 |
notified |
|
| 178 |
end |
|
| 179 | ||
| 180 |
def notified_mentions |
|
| 181 |
notified = super |
|
| 182 |
notified = select_journal_visible_user(notified) |
|
| 178 | 183 |
notified |
| 179 | 184 |
end |
| 180 | 185 | |
| ... | ... | |
| 337 | 342 |
Mailer.deliver_issue_edit(self) |
| 338 | 343 |
end |
| 339 | 344 |
end |
| 345 | ||
| 346 |
def select_journal_visible_user(notified) |
|
| 347 |
if private_notes? |
|
| 348 |
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
|
|
| 349 |
end |
|
| 350 |
notified |
|
| 351 |
end |
|
| 340 | 352 |
end |
| app/models/mailer.rb | ||
|---|---|---|
| 94 | 94 |
# Example: |
| 95 | 95 |
# Mailer.deliver_issue_add(issue) |
| 96 | 96 |
def self.deliver_issue_add(issue) |
| 97 |
users = issue.notified_users | issue.notified_watchers |
|
| 97 |
users = issue.notified_users | issue.notified_watchers | issue.notified_mentions
|
|
| 98 | 98 |
users.each do |user| |
| 99 | 99 |
issue_add(user, issue).deliver_later |
| 100 | 100 |
end |
| ... | ... | |
| 129 | 129 |
# Example: |
| 130 | 130 |
# Mailer.deliver_issue_edit(journal) |
| 131 | 131 |
def self.deliver_issue_edit(journal) |
| 132 |
users = journal.notified_users | journal.notified_watchers |
|
| 132 |
users = journal.notified_users | journal.notified_watchers | journal.notified_mentions | journal.journalized.notified_mentions
|
|
| 133 | 133 |
users.select! do |user| |
| 134 | 134 |
journal.notes? || journal.visible_details(user).any? |
| 135 | 135 |
end |
| ... | ... | |
| 222 | 222 |
# Example: |
| 223 | 223 |
# Mailer.deliver_news_added(news) |
| 224 | 224 |
def self.deliver_news_added(news) |
| 225 |
users = news.notified_users | news.notified_watchers_for_added_news |
|
| 225 |
users = news.notified_users | news.notified_watchers_for_added_news | news.notified_mentions
|
|
| 226 | 226 |
users.each do |user| |
| 227 | 227 |
news_added(user, news).deliver_later |
| 228 | 228 |
end |
| ... | ... | |
| 306 | 306 |
# Example: |
| 307 | 307 |
# Mailer.deliver_wiki_content_added(wiki_content) |
| 308 | 308 |
def self.deliver_wiki_content_added(wiki_content) |
| 309 |
users = wiki_content.notified_users | wiki_content.page.wiki.notified_watchers |
|
| 309 |
users = wiki_content.notified_users | wiki_content.page.wiki.notified_watchers | wiki_content.notified_mentions
|
|
| 310 | 310 |
users.each do |user| |
| 311 | 311 |
wiki_content_added(user, wiki_content).deliver_later |
| 312 | 312 |
end |
| ... | ... | |
| 343 | 343 |
users = wiki_content.notified_users |
| 344 | 344 |
users |= wiki_content.page.notified_watchers |
| 345 | 345 |
users |= wiki_content.page.wiki.notified_watchers |
| 346 |
users |= wiki_content.notified_mentions |
|
| 346 | 347 | |
| 347 | 348 |
users.each do |user| |
| 348 | 349 |
wiki_content_updated(user, wiki_content).deliver_later |
| app/models/news.rb | ||
|---|---|---|
| 35 | 35 |
acts_as_activity_provider :scope => proc {preload(:project, :author)},
|
| 36 | 36 |
:author_key => :author_id |
| 37 | 37 |
acts_as_watchable |
| 38 |
acts_as_mentionable :attributes => ['description'] |
|
| 38 | 39 | |
| 39 | 40 |
after_create :add_author_as_watcher |
| 40 | 41 |
after_create_commit :send_notification |
| app/models/wiki_content.rb | ||
|---|---|---|
| 24 | 24 |
belongs_to :page, :class_name => 'WikiPage' |
| 25 | 25 |
belongs_to :author, :class_name => 'User' |
| 26 | 26 |
has_many :versions, :class_name => 'WikiContentVersion', :dependent => :delete_all |
| 27 | ||
| 28 |
acts_as_mentionable :attributes => ['text'] |
|
| 29 | ||
| 27 | 30 |
validates_presence_of :text |
| 28 | 31 |
validates_length_of :comments, :maximum => 1024, :allow_nil => true |
| 29 | 32 | |
| config/routes.rb | ||
|---|---|---|
| 46 | 46 |
post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' |
| 47 | 47 |
post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' |
| 48 | 48 | |
| 49 |
# Auto complate routes
|
|
| 49 |
# Auto complete routes
|
|
| 50 | 50 |
match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' |
| 51 | 51 |
match '/wiki_pages/auto_complete', :to => 'auto_completes#wiki_pages', :via => :get, :as => 'auto_complete_wiki_pages' |
| 52 |
match '/users/auto_complete', :to => 'auto_completes#users', :via => :get, :as => 'auto_complete_users' |
|
| 52 | 53 | |
| 53 | 54 |
# Misc issue routes. TODO: move into resources |
| 54 | 55 |
match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] |
| lib/redmine.rb | ||
|---|---|---|
| 36 | 36 |
end |
| 37 | 37 | |
| 38 | 38 |
module Redmine |
| 39 | ||
| 39 | 40 |
end |
| lib/redmine/acts/mentionable.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006-2022 Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
module Redmine |
|
| 21 |
module Acts |
|
| 22 |
module Mentionable |
|
| 23 |
def self.included(base) |
|
| 24 |
base.extend ClassMethods |
|
| 25 |
end |
|
| 26 | ||
| 27 |
module ClassMethods |
|
| 28 |
def acts_as_mentionable(options = {})
|
|
| 29 |
class_attribute :mentionable_attributes |
|
| 30 |
self.mentionable_attributes = options[:attributes] |
|
| 31 | ||
| 32 |
attr_accessor :mentioned_users |
|
| 33 | ||
| 34 |
send :include, Redmine::Acts::Mentionable::InstanceMethods |
|
| 35 | ||
| 36 |
after_save :parse_mentions |
|
| 37 |
end |
|
| 38 |
end |
|
| 39 | ||
| 40 |
module InstanceMethods |
|
| 41 |
def self.included(base) |
|
| 42 |
base.extend ClassMethods |
|
| 43 |
end |
|
| 44 | ||
| 45 |
def notified_mentions |
|
| 46 |
notified = mentioned_users.to_a |
|
| 47 |
notified.reject! {|user| user.mail.blank? || user.mail_notification == 'none'}
|
|
| 48 |
if respond_to?(:visible?) |
|
| 49 |
notified.reject! {|user| !visible?(user)}
|
|
| 50 |
end |
|
| 51 |
notified |
|
| 52 |
end |
|
| 53 | ||
| 54 |
private |
|
| 55 | ||
| 56 |
def parse_mentions |
|
| 57 |
mentionable_attrs = self.mentionable_attributes |
|
| 58 |
saved_mentionable_attrs = self.saved_changes.select{|a| mentionable_attrs.include?(a)}
|
|
| 59 | ||
| 60 |
saved_mentionable_attrs.each do |key, attr| |
|
| 61 |
old_value, new_value = attr |
|
| 62 |
get_mentioned_users(old_value, new_value) |
|
| 63 |
end |
|
| 64 |
end |
|
| 65 | ||
| 66 |
def get_mentioned_users(old_content, new_content) |
|
| 67 |
self.mentioned_users = [] |
|
| 68 | ||
| 69 |
previous_matches = scan_for_mentioned_users(old_content) |
|
| 70 |
current_matches = scan_for_mentioned_users(new_content) |
|
| 71 |
new_matches = (current_matches - previous_matches).flatten |
|
| 72 | ||
| 73 |
if new_matches.any? |
|
| 74 |
self.mentioned_users = User.visible.active.where(login: new_matches) |
|
| 75 |
end |
|
| 76 |
end |
|
| 77 | ||
| 78 |
def scan_for_mentioned_users(content) |
|
| 79 |
return [] if content.nil? |
|
| 80 | ||
| 81 |
# remove quoted text |
|
| 82 |
content = content.gsub(%r{\r\n(?:\>\s)+(.*?)\r\n}m, '')
|
|
| 83 | ||
| 84 |
text_formatting = Setting.text_formatting |
|
| 85 |
# Remove text wrapped in pre tags based on text formatting |
|
| 86 |
if text_formatting == 'textile' |
|
| 87 |
content = content.gsub(%r{<pre>(.*?)</pre>}m, '')
|
|
| 88 |
elsif text_formatting == 'markdown' || text_formatting == 'common_mark' |
|
| 89 |
content = content.gsub(%r{(~~~|```)(.*?)(~~~|```)}m, '')
|
|
| 90 |
end |
|
| 91 | ||
| 92 |
users = content.scan(MentionPattern).flatten |
|
| 93 |
end |
|
| 94 | ||
| 95 |
MentionPattern = / |
|
| 96 |
(?:^|\W) # beginning of string or non-word char |
|
| 97 |
@((?>[a-z0-9][a-z0-9-]*)) # @username |
|
| 98 |
(?!\/) # without a trailing slash |
|
| 99 |
(?= |
|
| 100 |
\.+[ \t\W]| # dots followed by space or non-word character |
|
| 101 |
\.+$| # dots at end of line |
|
| 102 |
[^0-9a-zA-Z_.]| # non-word character except dot |
|
| 103 |
$ # end of line |
|
| 104 |
) |
|
| 105 |
/ix |
|
| 106 |
end |
|
| 107 |
end |
|
| 108 |
end |
|
| 109 |
end |
|
| lib/redmine/preparation.rb | ||
|---|---|---|
| 21 | 21 |
module Preparation |
| 22 | 22 |
def self.prepare |
| 23 | 23 |
ActiveRecord::Base.include Redmine::Acts::Positioned |
| 24 |
ActiveRecord::Base.include Redmine::Acts::Mentionable |
|
| 24 | 25 |
ActiveRecord::Base.include Redmine::I18n |
| 25 | 26 | |
| 26 | 27 |
Scm::Base.add "Subversion" |
| public/javascripts/application.js | ||
|---|---|---|
| 1193 | 1193 |
noMatchTemplate: function () {
|
| 1194 | 1194 |
return '<span style:"visibility: hidden;"></span>'; |
| 1195 | 1195 |
} |
| 1196 |
}, |
|
| 1197 |
{
|
|
| 1198 |
trigger: '@', |
|
| 1199 |
lookup: function (user, mentionText) {
|
|
| 1200 |
return user.name + user.firstname + user.lastname + user.login; |
|
| 1201 |
}, |
|
| 1202 |
values: function (text, cb) {
|
|
| 1203 |
remoteSearch(getDataSource('users') + text, function (users) {
|
|
| 1204 |
return cb(users); |
|
| 1205 |
}); |
|
| 1206 |
}, |
|
| 1207 |
menuItemTemplate: function (user) {
|
|
| 1208 |
return user.original.name; |
|
| 1209 |
}, |
|
| 1210 |
selectTemplate: function (user) {
|
|
| 1211 |
return '@' + user.original.login; |
|
| 1212 |
} |
|
| 1196 | 1213 |
} |
| 1197 | 1214 |
] |
| 1198 | 1215 |
}); |
| test/functional/auto_completes_controller_test.rb | ||
|---|---|---|
| 79 | 79 |
assert_include "Bug #13", response.body |
| 80 | 80 |
end |
| 81 | 81 | |
| 82 |
def test_auto_complete_with_scope_all_should_search_other_projects
|
|
| 82 |
def test_issues_with_scope_all_should_search_other_projects
|
|
| 83 | 83 |
get( |
| 84 | 84 |
:issues, |
| 85 | 85 |
:params => {
|
| ... | ... | |
| 92 | 92 |
assert_include "Bug #13", response.body |
| 93 | 93 |
end |
| 94 | 94 | |
| 95 |
def test_auto_complete_without_project_should_search_all_projects
|
|
| 95 |
def test_issues_without_project_should_search_all_projects
|
|
| 96 | 96 |
get(:issues, :params => {:q => '13'})
|
| 97 | 97 |
assert_response :success |
| 98 | 98 |
assert_include "Bug #13", response.body |
| 99 | 99 |
end |
| 100 | 100 | |
| 101 |
def test_auto_complete_without_scope_all_should_not_search_other_projects
|
|
| 101 |
def test_issues_without_scope_all_should_not_search_other_projects
|
|
| 102 | 102 |
get( |
| 103 | 103 |
:issues, |
| 104 | 104 |
:params => {
|
| ... | ... | |
| 128 | 128 |
assert_equal 'Bug #13: Subproject issue two', issue['label'] |
| 129 | 129 |
end |
| 130 | 130 | |
| 131 |
def test_auto_complete_with_status_o_should_return_open_issues_only
|
|
| 131 |
def test_issues_with_status_o_should_return_open_issues_only
|
|
| 132 | 132 |
get( |
| 133 | 133 |
:issues, |
| 134 | 134 |
:params => {
|
| ... | ... | |
| 142 | 142 |
assert_not_include "closed", response.body |
| 143 | 143 |
end |
| 144 | 144 | |
| 145 |
def test_auto_complete_with_status_c_should_return_closed_issues_only
|
|
| 145 |
def test_issues_with_status_c_should_return_closed_issues_only
|
|
| 146 | 146 |
get( |
| 147 | 147 |
:issues, |
| 148 | 148 |
:params => {
|
| ... | ... | |
| 156 | 156 |
assert_not_include "Issue due today", response.body |
| 157 | 157 |
end |
| 158 | 158 | |
| 159 |
def test_auto_complete_with_issue_id_should_not_return_that_issue
|
|
| 159 |
def test_issues_with_issue_id_should_not_return_that_issue
|
|
| 160 | 160 |
get( |
| 161 | 161 |
:issues, |
| 162 | 162 |
:params => {
|
| ... | ... | |
| 182 | 182 |
assert_include 'application/json', response.headers['Content-Type'] |
| 183 | 183 |
end |
| 184 | 184 | |
| 185 |
def test_auto_complete_without_term_should_return_last_10_issues
|
|
| 185 |
def test_issue_without_term_should_return_last_10_issues
|
|
| 186 | 186 |
# There are 9 issues generated by fixtures |
| 187 | 187 |
# and we need two more to test the 10 limit |
| 188 | 188 |
%w(1..2).each do |
| ... | ... | |
| 269 | 269 |
assert_equal 10, json.count |
| 270 | 270 |
assert_equal wiki.pages[-2].id, json.first['id'].to_i |
| 271 | 271 |
end |
| 272 | ||
| 273 |
def test_users_should_accept_case_insensitive_term_param |
|
| 274 |
get :users, :params => {
|
|
| 275 |
:q => 'JoHN' |
|
| 276 |
} |
|
| 277 |
assert_response :success |
|
| 278 |
assert_include "John Smith", response.body |
|
| 279 |
end |
|
| 280 | ||
| 281 |
def test_users_should_return_json |
|
| 282 |
get :users, :params => {
|
|
| 283 |
:q => 'john' |
|
| 284 |
} |
|
| 285 |
assert_response :success |
|
| 286 |
json = ActiveSupport::JSON.decode(response.body) |
|
| 287 |
assert_kind_of Array, json |
|
| 288 |
user = json.first |
|
| 289 |
assert_kind_of Hash, user |
|
| 290 |
assert_equal 'John', user['firstname'] |
|
| 291 |
assert_equal 'Smith', user['lastname'] |
|
| 292 |
assert_equal 'John Smith', user['name'] |
|
| 293 |
assert_equal 'jsmith', user['login'] |
|
| 294 |
end |
|
| 295 | ||
| 296 |
def test_users_with_project_and_without_term_should_return_project_users |
|
| 297 |
get :users, :params => {
|
|
| 298 |
:project_id => 'onlinestore' |
|
| 299 |
} |
|
| 300 |
assert_response :success |
|
| 301 |
json = ActiveSupport::JSON.decode(response.body) |
|
| 302 |
assert_equal 2, json.count |
|
| 303 |
assert_equal 'jsmith', json.first['login'] |
|
| 304 |
assert_equal 'miscuser8', json.last['login'] |
|
| 305 |
end |
|
| 306 | ||
| 307 |
def test_users_without_project_and_with_term_should_search_in_all_users |
|
| 308 |
get :users, :params => {
|
|
| 309 |
:q => 'm' |
|
| 310 |
} |
|
| 311 |
assert_response :success |
|
| 312 |
assert_include "Redmine Admin", response.body |
|
| 313 |
assert_include "Dave Lopper", response.body |
|
| 314 |
end |
|
| 272 | 315 |
end |
| test/unit/journal_test.rb | ||
|---|---|---|
| 236 | 236 |
assert_equal "image#{i}.png", attachment.filename
|
| 237 | 237 |
end |
| 238 | 238 |
end |
| 239 | ||
| 240 |
def test_notified_mentions_should_not_include_users_who_cannot_view_private_notes |
|
| 241 |
journal = Journal.generate!(journalized: Issue.find(2), user: User.find(1), private_notes: true, notes: 'Hello @dlopper, @jsmith and @admin.') |
|
| 242 | ||
| 243 |
# User "dlopper" has "Developer" role on project "eCookbook" |
|
| 244 |
# Role "Developer" does not have the "View private notes" permission |
|
| 245 |
assert_equal [1, 2], journal.notified_mentions.map(&:id) |
|
| 246 |
end |
|
| 239 | 247 |
end |
| test/unit/lib/redmine/acts/mentionable_test.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006-2022 Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
require File.expand_path('../../../../../test_helper', __FILE__)
|
|
| 21 | ||
| 22 |
class Redmine::Acts::MentionableTest < ActiveSupport::TestCase |
|
| 23 |
fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, |
|
| 24 |
:groups_users, |
|
| 25 |
:trackers, :projects_trackers, |
|
| 26 |
:enabled_modules, |
|
| 27 |
:issue_statuses, :issue_categories, :issue_relations, :workflows, |
|
| 28 |
:enumerations, |
|
| 29 |
:issues |
|
| 30 | ||
| 31 |
def test_mentioned_users_with_user_mention |
|
| 32 |
issue = Issue.generate!(project_id: 1, description: '@dlopper') |
|
| 33 | ||
| 34 |
assert_equal [User.find(3)], issue.mentioned_users |
|
| 35 |
end |
|
| 36 | ||
| 37 |
def test_mentioned_users_with_multiple_mentions |
|
| 38 |
issue = Issue.generate!(project_id: 1, description: 'Hello @dlopper, @jsmith.') |
|
| 39 | ||
| 40 |
assert_equal [User.find(2), User.find(3)], issue.mentioned_users |
|
| 41 |
end |
|
| 42 | ||
| 43 |
def test_mentioned_users_should_not_mention_same_user_multiple_times |
|
| 44 |
issue = Issue.generate!(project_id: 1, description: '@dlopper @jsmith @dlopper') |
|
| 45 | ||
| 46 |
assert_equal [User.find(2), User.find(3)], issue.mentioned_users |
|
| 47 |
end |
|
| 48 | ||
| 49 |
def test_mentioned_users_should_include_only_active_users |
|
| 50 |
# disable dlopper account |
|
| 51 |
user = User.find(3) |
|
| 52 |
user.status = User::STATUS_LOCKED |
|
| 53 |
user.save |
|
| 54 | ||
| 55 |
issue = Issue.generate!(project_id: 1, description: '@dlopper @jsmith') |
|
| 56 | ||
| 57 |
assert_equal [User.find(2)], issue.mentioned_users |
|
| 58 |
end |
|
| 59 | ||
| 60 |
def test_mentioned_users_should_include_only_visible_users |
|
| 61 |
User.current = nil |
|
| 62 |
Role.non_member.update! users_visibility: 'members_of_visible_projects' |
|
| 63 |
Role.anonymous.update! users_visibility: 'members_of_visible_projects' |
|
| 64 |
user = User.generate! |
|
| 65 | ||
| 66 |
issue = Issue.generate!(project_id: 1, description: "@jsmith @#{user.login}")
|
|
| 67 | ||
| 68 |
assert_equal [User.find(2)], issue.mentioned_users |
|
| 69 |
end |
|
| 70 | ||
| 71 |
def test_mentioned_users_should_not_include_mentioned_users_in_existing_content |
|
| 72 |
issue = Issue.generate!(project_id: 1, description: 'Hello @dlopper') |
|
| 73 | ||
| 74 |
assert issue.save |
|
| 75 |
assert_equal [User.find(3)], issue.mentioned_users |
|
| 76 | ||
| 77 |
issue.description = 'Hello @dlopper and @jsmith' |
|
| 78 |
issue.save |
|
| 79 | ||
| 80 |
assert_equal [User.find(2)], issue.mentioned_users |
|
| 81 |
end |
|
| 82 | ||
| 83 |
def test_mentioned_users_should_not_include_users_wrapped_in_pre_tags_for_textile |
|
| 84 |
description = <<~STR |
|
| 85 |
<pre> |
|
| 86 |
Hello @jsmith |
|
| 87 |
</pre> |
|
| 88 |
STR |
|
| 89 | ||
| 90 |
with_settings text_formatting: 'textile' do |
|
| 91 |
issue = Issue.generate!(project_id: 1, description: description) |
|
| 92 | ||
| 93 |
assert_equal [], issue.mentioned_users |
|
| 94 |
end |
|
| 95 |
end |
|
| 96 | ||
| 97 |
def test_mentioned_users_should_not_include_users_wrapped_in_pre_tags_for_markdown |
|
| 98 |
description = <<~STR |
|
| 99 |
``` |
|
| 100 |
Hello @jsmith |
|
| 101 |
``` |
|
| 102 |
STR |
|
| 103 | ||
| 104 |
with_settings text_formatting: 'markdown' do |
|
| 105 |
issue = Issue.generate!(project_id: 1, description: description) |
|
| 106 | ||
| 107 |
assert_equal [], issue.mentioned_users |
|
| 108 |
end |
|
| 109 |
end |
|
| 110 | ||
| 111 |
def test_mentioned_users_should_not_include_users_wrapped_in_pre_tags_for_common_mark |
|
| 112 |
description = <<~STR |
|
| 113 |
``` |
|
| 114 |
Hello @jsmith |
|
| 115 |
``` |
|
| 116 |
STR |
|
| 117 | ||
| 118 |
with_settings text_formatting: 'common_mark' do |
|
| 119 |
issue = Issue.generate!(project_id: 1, description: description) |
|
| 120 | ||
| 121 |
assert_equal [], issue.mentioned_users |
|
| 122 |
end |
|
| 123 |
end |
|
| 124 | ||
| 125 |
def test_notified_mentions |
|
| 126 |
issue = Issue.generate!(project_id: 1, description: 'Hello @dlopper, @jsmith.') |
|
| 127 | ||
| 128 |
assert_equal [User.find(2), User.find(3)], issue.notified_mentions |
|
| 129 |
end |
|
| 130 | ||
| 131 |
def test_notified_mentions_should_not_include_users_who_out_of_all_email |
|
| 132 |
User.find(3).update!(mail_notification: :none) |
|
| 133 |
issue = Issue.generate!(project_id: 1, description: "Hello @dlopper, @jsmith.") |
|
| 134 | ||
| 135 |
assert_equal [User.find(2)], issue.notified_mentions |
|
| 136 |
end |
|
| 137 | ||
| 138 |
def test_notified_mentions_should_not_include_users_who_cannot_view_the_object |
|
| 139 |
user = User.find(3) |
|
| 140 | ||
| 141 |
# User dlopper does not have access to project "Private child of eCookbook" |
|
| 142 |
issue = Issue.generate!(project_id: 5, description: "Hello @dlopper, @jsmith.") |
|
| 143 | ||
| 144 |
assert !issue.notified_mentions.include?(user) |
|
| 145 |
end |
|
| 146 |
end |
|
| test/unit/mailer_test.rb | ||
|---|---|---|
| 464 | 464 |
assert_not_include user.mail, recipients |
| 465 | 465 |
end |
| 466 | 466 | |
| 467 |
def test_issue_add_should_notify_mentioned_users_in_issue_description |
|
| 468 |
User.find(1).mail_notification = 'only_my_events' |
|
| 469 | ||
| 470 |
issue = Issue.generate!(project_id: 1, description: 'Hello @dlopper and @admin.') |
|
| 471 | ||
| 472 |
assert Mailer.deliver_issue_add(issue) |
|
| 473 |
# @jsmith and @dlopper are members of the project |
|
| 474 |
# admin is mentioned |
|
| 475 |
# @dlopper won't receive duplicated notifications |
|
| 476 |
assert_equal 3, ActionMailer::Base.deliveries.size |
|
| 477 |
assert_include User.find(1).mail, recipients |
|
| 478 |
end |
|
| 479 | ||
| 467 | 480 |
def test_issue_add_should_include_enabled_fields |
| 468 | 481 |
issue = Issue.find(2) |
| 469 | 482 |
assert Mailer.deliver_issue_add(issue) |
| ... | ... | |
| 608 | 621 |
end |
| 609 | 622 |
end |
| 610 | 623 | |
| 624 |
def test_issue_edit_should_notify_mentioned_users_in_issue_updated_description |
|
| 625 |
User.find(1).mail_notification = 'only_my_events' |
|
| 626 | ||
| 627 |
issue = Issue.find(3) |
|
| 628 |
issue.init_journal(User.current) |
|
| 629 |
issue.update(description: "Hello @admin") |
|
| 630 |
journal = issue.journals.last |
|
| 631 | ||
| 632 |
ActionMailer::Base.deliveries.clear |
|
| 633 |
Mailer.deliver_issue_edit(journal) |
|
| 634 | ||
| 635 |
# @jsmith and @dlopper are members of the project |
|
| 636 |
# admin is mentioned in the updated description |
|
| 637 |
# @dlopper won't receive duplicated notifications |
|
| 638 |
assert_equal 3, ActionMailer::Base.deliveries.size |
|
| 639 |
assert_include User.find(1).mail, recipients |
|
| 640 |
end |
|
| 641 | ||
| 642 |
def test_issue_edit_should_notify_mentioned_users_in_notes |
|
| 643 |
User.find(1).mail_notification = 'only_my_events' |
|
| 644 | ||
| 645 |
journal = Journal.generate!(journalized: Issue.find(3), user: User.find(1), notes: 'Hello @admin.') |
|
| 646 | ||
| 647 |
ActionMailer::Base.deliveries.clear |
|
| 648 |
Mailer.deliver_issue_edit(journal) |
|
| 649 | ||
| 650 |
# @jsmith and @dlopper are members of the project |
|
| 651 |
# admin is mentioned in the notes |
|
| 652 |
# @dlopper won't receive duplicated notifications |
|
| 653 |
assert_equal 3, ActionMailer::Base.deliveries.size |
|
| 654 |
assert_include User.find(1).mail, recipients |
|
| 655 |
end |
|
| 656 | ||
| 611 | 657 |
def test_issue_should_send_email_notification_with_suppress_empty_fields |
| 612 | 658 |
ActionMailer::Base.deliveries.clear |
| 613 | 659 |
with_settings :notified_events => %w(issue_added) do |
| ... | ... | |
| 691 | 737 |
end |
| 692 | 738 |
end |
| 693 | 739 | |
| 740 |
def test_news_added_should_notify_mentioned_users_in_news_description |
|
| 741 |
news = News.new(title: 'Test news', description: 'Hello @admin.', author: User.first, project_id: 1) |
|
| 742 |
news.save! |
|
| 743 | ||
| 744 |
ActionMailer::Base.deliveries.clear |
|
| 745 |
Mailer.deliver_news_added(news) |
|
| 746 | ||
| 747 |
# @jsmith and @dlopper are members of the project |
|
| 748 |
# admin is mentioned in the notes |
|
| 749 |
# @dlopper won't receive duplicated notifications |
|
| 750 |
assert_equal 3, ActionMailer::Base.deliveries.size |
|
| 751 |
assert_include User.find(1).mail, recipients |
|
| 752 |
end |
|
| 753 | ||
| 694 | 754 |
def test_wiki_content_added |
| 695 | 755 |
content = WikiContent.find(1) |
| 696 | 756 |
assert_difference 'ActionMailer::Base.deliveries.size', 2 do |
| ... | ... | |
| 703 | 763 |
end |
| 704 | 764 |
end |
| 705 | 765 | |
| 766 |
def test_wiki_content_added_should_notify_mentioned_users_in_content |
|
| 767 |
content = WikiContent.new(text: 'Hello @admin.', author_id: 1, page_id: 1) |
|
| 768 |
content.save! |
|
| 769 | ||
| 770 |
ActionMailer::Base.deliveries.clear |
|
| 771 |
Mailer.deliver_wiki_content_added(content) |
|
| 772 | ||
| 773 |
# @jsmith and @dlopper are members of the project |
|
| 774 |
# admin is mentioned in the notes |
|
| 775 |
# @dlopper won't receive duplicated notifications |
|
| 776 |
assert_equal 3, ActionMailer::Base.deliveries.size |
|
| 777 |
assert_include User.find(1).mail, recipients |
|
| 778 |
end |
|
| 779 | ||
| 706 | 780 |
def test_wiki_content_updated |
| 707 | 781 |
content = WikiContent.find(1) |
| 708 | 782 |
assert Mailer.deliver_wiki_content_updated(content) |
| ... | ... | |
| 713 | 787 |
end |
| 714 | 788 |
end |
| 715 | 789 | |
| 790 |
def test_wiki_content_updated_should_notify_mentioned_users_in_updated_content |
|
| 791 |
content = WikiContent.find(1) |
|
| 792 |
content.update(text: 'Hello @admin.') |
|
| 793 |
content.save! |
|
| 794 | ||
| 795 |
ActionMailer::Base.deliveries.clear |
|
| 796 |
Mailer.deliver_wiki_content_updated(content) |
|
| 797 | ||
| 798 |
# @jsmith and @dlopper are members of the project |
|
| 799 |
# admin is mentioned in the notes |
|
| 800 |
# @dlopper won't receive duplicated notifications |
|
| 801 |
assert_equal 3, ActionMailer::Base.deliveries.size |
|
| 802 |
assert_include User.find(1).mail, recipients |
|
| 803 |
end |
|
| 804 | ||
| 716 | 805 |
def test_register |
| 717 | 806 |
token = Token.find(1) |
| 718 | 807 |
assert Mailer.deliver_register(token.user, token) |