Commit 20ed39e781fc49bd4ec6e04e862ebd4ece53a15b

Authored by Dmitriy Zaporozhets
2 parents a324960d cf35d19c

Merge pull request #3898 from jacargentina/notifymentioned

Notify email to mentioned users; show participants ala github on issuables
app/assets/stylesheets/sections/issues.scss
... ... @@ -106,3 +106,7 @@ input.check_all_issues {
106 106 #update_status {
107 107 width: 100px;
108 108 }
  109 +
  110 +.participants {
  111 + margin-bottom: 10px;
  112 +}
... ...
app/helpers/projects_helper.rb
... ... @@ -17,7 +17,7 @@ module ProjectsHelper
17 17 end
18 18  
19 19 def link_to_member(project, author, opts = {})
20   - default_opts = { avatar: true }
  20 + default_opts = { avatar: true, name: true, size: 16 }
21 21 opts = default_opts.merge(opts)
22 22  
23 23 return "(deleted)" unless author
... ... @@ -25,10 +25,10 @@ module ProjectsHelper
25 25 author_html = ""
26 26  
27 27 # Build avatar image tag
28   - author_html << image_tag(gravatar_icon(author.try(:email)), width: 16, class: "avatar avatar-inline s16") if opts[:avatar]
  28 + author_html << image_tag(gravatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}") if opts[:avatar]
29 29  
30 30 # Build name span tag
31   - author_html << content_tag(:span, sanitize(author.name), class: 'author')
  31 + author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name]
32 32  
33 33 author_html = author_html.html_safe
34 34  
... ...
app/models/concerns/issuable.rb
... ... @@ -6,6 +6,7 @@
6 6 #
7 7 module Issuable
8 8 extend ActiveSupport::Concern
  9 + include Mentionable
9 10  
10 11 included do
11 12 belongs_to :project
... ... @@ -97,4 +98,18 @@ module Issuable
97 98 def votes_count
98 99 upvotes + downvotes
99 100 end
  101 +
  102 + # Return all users participating on the discussion
  103 + def participants
  104 + users = []
  105 + users << author
  106 + users << assignee if is_assigned?
  107 + mentions = []
  108 + mentions << self.mentioned_users
  109 + notes.each do |note|
  110 + users << note.author
  111 + mentions << note.mentioned_users
  112 + end
  113 + users.concat(mentions.reduce([], :|)).uniq
  114 + end
100 115 end
... ...
app/models/concerns/mentionable.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +# == Mentionable concern
  2 +#
  3 +# Contains common functionality shared between Issues and Notes
  4 +#
  5 +# Used by Issue, Note
  6 +#
  7 +module Mentionable
  8 + extend ActiveSupport::Concern
  9 +
  10 + def mentioned_users
  11 + users = []
  12 + return users if mentionable_text.blank?
  13 + has_project = self.respond_to? :project
  14 + matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/)
  15 + matches.each do |match|
  16 + identifier = match.delete "@"
  17 + if has_project
  18 + id = project.users_projects.joins(:user).where(users: { username: identifier }).pluck(:user_id).first
  19 + else
  20 + id = User.where(username: identifier).pluck(:id).first
  21 + end
  22 + users << User.find(id) unless id.blank?
  23 + end
  24 + users.uniq
  25 + end
  26 +
  27 + def mentionable_text
  28 + if self.class == Issue
  29 + description
  30 + elsif self.class == Note
  31 + note
  32 + else
  33 + nil
  34 + end
  35 + end
  36 +
  37 +end
... ...
app/models/note.rb
... ... @@ -19,6 +19,8 @@ require &#39;carrierwave/orm/activerecord&#39;
19 19 require 'file_size_validator'
20 20  
21 21 class Note < ActiveRecord::Base
  22 + include Mentionable
  23 +
22 24 attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
23 25 :attachment, :line_code, :commit_id
24 26  
... ...
app/services/notification_service.rb
... ... @@ -110,9 +110,11 @@ class NotificationService
110 110 else
111 111 opts.merge!(noteable_id: note.noteable_id)
112 112 target = note.noteable
113   - recipients = []
114   - recipients << target.assignee if target.respond_to?(:assignee)
115   - recipients << target.author if target.respond_to?(:author)
  113 + if target.respond_to?(:participants)
  114 + recipients = target.participants
  115 + else
  116 + recipients = []
  117 + end
116 118 end
117 119  
118 120 # Get users who left comment in thread
... ... @@ -181,7 +183,12 @@ class NotificationService
181 183 end
182 184  
183 185 def new_resource_email(target, method)
184   - recipients = reject_muted_users([target.assignee], target.project)
  186 + if target.respond_to?(:participants)
  187 + recipients = target.participants
  188 + else
  189 + recipients = []
  190 + end
  191 + recipients = reject_muted_users(recipients, target.project)
185 192 recipients = recipients.concat(project_watchers(target.project)).uniq
186 193 recipients.delete(target.author)
187 194  
... ...
app/views/issues/show.html.haml
... ... @@ -65,4 +65,9 @@
65 65 - else
66 66 = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
67 67  
  68 +.participants
  69 + %cite.cgray #{@issue.participants.count} participants
  70 + - @issue.participants.each do |participant|
  71 + = link_to_member(@project, participant, name: false, size: 24)
  72 +
68 73 .voting_notes#notes= render "notes/notes_with_form"
... ...
spec/services/notification_service_spec.rb
... ... @@ -19,7 +19,7 @@ describe NotificationService do
19 19 describe 'Notes' do
20 20 context 'issue note' do
21 21 let(:issue) { create(:issue, assignee: create(:user)) }
22   - let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) }
  22 + let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced') }
23 23  
24 24 before do
25 25 build_team(note.project)
... ... @@ -30,6 +30,7 @@ describe NotificationService do
30 30 should_email(@u_watcher.id)
31 31 should_email(note.noteable.author_id)
32 32 should_email(note.noteable.assignee_id)
  33 + should_email(@u_mentioned.id)
33 34 should_not_email(note.author_id)
34 35 should_not_email(@u_participating.id)
35 36 should_not_email(@u_disabled.id)
... ... @@ -235,9 +236,11 @@ describe NotificationService do
235 236 @u_watcher = create(:user, notification_level: Notification::N_WATCH)
236 237 @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)
237 238 @u_disabled = create(:user, notification_level: Notification::N_DISABLED)
  239 + @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_WATCH)
238 240  
239 241 project.team << [@u_watcher, :master]
240 242 project.team << [@u_participating, :master]
241 243 project.team << [@u_disabled, :master]
  244 + project.team << [@u_mentioned, :master]
242 245 end
243 246 end
... ...