diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 5a1b476fe25..ed7902fec3a 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -106,3 +106,7 @@ input.check_all_issues { #update_status { width: 100px; } + +.participants { + margin-bottom: 10px; +} diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 878b0913769..39290ec05bb 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -17,7 +17,7 @@ module ProjectsHelper end def link_to_member(project, author, opts = {}) - default_opts = { avatar: true } + default_opts = { avatar: true, name: true, size: 16 } opts = default_opts.merge(opts) return "(deleted)" unless author @@ -25,10 +25,10 @@ module ProjectsHelper author_html = "" # Build avatar image tag - author_html << image_tag(gravatar_icon(author.try(:email)), width: 16, class: "avatar avatar-inline s16") if opts[:avatar] + 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] # Build name span tag - author_html << content_tag(:span, sanitize(author.name), class: 'author') + author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name] author_html = author_html.html_safe diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 11e3d8eed19..38440859064 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -6,6 +6,7 @@ # module Issuable extend ActiveSupport::Concern + include Mentionable included do belongs_to :project @@ -97,4 +98,18 @@ module Issuable def votes_count upvotes + downvotes end + + # Return all users participating on the discussion + def participants + users = [] + users << author + users << assignee if is_assigned? + mentions = [] + mentions << self.mentioned_users + notes.each do |note| + users << note.author + mentions << note.mentioned_users + end + users.concat(mentions.reduce([], :|)).uniq + end end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb new file mode 100644 index 00000000000..f22070f8504 --- /dev/null +++ b/app/models/concerns/mentionable.rb @@ -0,0 +1,37 @@ +# == Mentionable concern +# +# Contains common functionality shared between Issues and Notes +# +# Used by Issue, Note +# +module Mentionable + extend ActiveSupport::Concern + + def mentioned_users + users = [] + return users if mentionable_text.blank? + has_project = self.respond_to? :project + matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/) + matches.each do |match| + identifier = match.delete "@" + if has_project + id = project.users_projects.joins(:user).where(users: { username: identifier }).pluck(:user_id).first + else + id = User.where(username: identifier).pluck(:id).first + end + users << User.find(id) unless id.blank? + end + users.uniq + end + + def mentionable_text + if self.class == Issue + description + elsif self.class == Note + note + else + nil + end + end + +end diff --git a/app/models/note.rb b/app/models/note.rb index 9a3481faaaa..56a8749e47d 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -19,6 +19,8 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class Note < ActiveRecord::Base + include Mentionable + attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id, :attachment, :line_code, :commit_id diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 379d2c54629..b0243481b35 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -110,9 +110,11 @@ class NotificationService else opts.merge!(noteable_id: note.noteable_id) target = note.noteable - recipients = [] - recipients << target.assignee if target.respond_to?(:assignee) - recipients << target.author if target.respond_to?(:author) + if target.respond_to?(:participants) + recipients = target.participants + else + recipients = [] + end end # Get users who left comment in thread @@ -181,7 +183,12 @@ class NotificationService end def new_resource_email(target, method) - recipients = reject_muted_users([target.assignee], target.project) + if target.respond_to?(:participants) + recipients = target.participants + else + recipients = [] + end + recipients = reject_muted_users(recipients, target.project) recipients = recipients.concat(project_watchers(target.project)).uniq recipients.delete(target.author) diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml index 2e204b8240d..891a01f14c9 100644 --- a/app/views/issues/show.html.haml +++ b/app/views/issues/show.html.haml @@ -65,4 +65,9 @@ - else = 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" +.participants + %cite.cgray #{@issue.participants.count} participants + - @issue.participants.each do |participant| + = link_to_member(@project, participant, name: false, size: 24) + .voting_notes#notes= render "notes/notes_with_form" diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 21e4202f4ee..76501482303 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -19,7 +19,7 @@ describe NotificationService do describe 'Notes' do context 'issue note' do let(:issue) { create(:issue, assignee: create(:user)) } - let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) } + let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced') } before do build_team(note.project) @@ -30,6 +30,7 @@ describe NotificationService do should_email(@u_watcher.id) should_email(note.noteable.author_id) should_email(note.noteable.assignee_id) + should_email(@u_mentioned.id) should_not_email(note.author_id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) @@ -235,9 +236,11 @@ describe NotificationService do @u_watcher = create(:user, notification_level: Notification::N_WATCH) @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) @u_disabled = create(:user, notification_level: Notification::N_DISABLED) + @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_WATCH) project.team << [@u_watcher, :master] project.team << [@u_participating, :master] project.team << [@u_disabled, :master] + project.team << [@u_mentioned, :master] end end