Commit d41940d36ee8ff693a3c22d6c317e864b53792f0

Authored by Dmitriy Zaporozhets
2 parents 106764ab 884498c5

Merge branch 'master' into 6-0-dev

Conflicts:
	app/views/projects/issues/show.html.haml
	db/fixtures/development/09_issues.rb
	db/fixtures/development/10_merge_requests.rb
app/assets/javascripts/dispatcher.js.coffee
1 $ -> 1 $ ->
2 new Dispatcher() 2 new Dispatcher()
3 - 3 +
4 class Dispatcher 4 class Dispatcher
5 constructor: () -> 5 constructor: () ->
6 @initSearch() 6 @initSearch()
app/assets/stylesheets/sections/issues.scss
@@ -106,3 +106,7 @@ input.check_all_issues { @@ -106,3 +106,7 @@ input.check_all_issues {
106 #update_status { 106 #update_status {
107 width: 100px; 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,7 +17,7 @@ module ProjectsHelper
17 end 17 end
18 18
19 def link_to_member(project, author, opts = {}) 19 def link_to_member(project, author, opts = {})
20 - default_opts = { avatar: true } 20 + default_opts = { avatar: true, name: true, size: 16 }
21 opts = default_opts.merge(opts) 21 opts = default_opts.merge(opts)
22 22
23 return "(deleted)" unless author 23 return "(deleted)" unless author
@@ -25,10 +25,10 @@ module ProjectsHelper @@ -25,10 +25,10 @@ module ProjectsHelper
25 author_html = "" 25 author_html = ""
26 26
27 # Build avatar image tag 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 # Build name span tag 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 author_html = author_html.html_safe 33 author_html = author_html.html_safe
34 34
app/models/concerns/issuable.rb
@@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
6 # 6 #
7 module Issuable 7 module Issuable
8 extend ActiveSupport::Concern 8 extend ActiveSupport::Concern
  9 + include Mentionable
9 10
10 included do 11 included do
11 belongs_to :project 12 belongs_to :project
@@ -96,4 +97,18 @@ module Issuable @@ -96,4 +97,18 @@ module Issuable
96 def votes_count 97 def votes_count
97 upvotes + downvotes 98 upvotes + downvotes
98 end 99 end
  100 +
  101 + # Return all users participating on the discussion
  102 + def participants
  103 + users = []
  104 + users << author
  105 + users << assignee if is_assigned?
  106 + mentions = []
  107 + mentions << self.mentioned_users
  108 + notes.each do |note|
  109 + users << note.author
  110 + mentions << note.mentioned_users
  111 + end
  112 + users.concat(mentions.reduce([], :|)).uniq
  113 + end
99 end 114 end
app/models/concerns/mentionable.rb 0 → 100644
@@ -0,0 +1,37 @@ @@ -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,6 +19,8 @@ require &#39;carrierwave/orm/activerecord&#39;
19 require 'file_size_validator' 19 require 'file_size_validator'
20 20
21 class Note < ActiveRecord::Base 21 class Note < ActiveRecord::Base
  22 + include Mentionable
  23 +
22 attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id, 24 attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
23 :attachment, :line_code, :commit_id 25 :attachment, :line_code, :commit_id
24 26
app/services/notification_service.rb
@@ -110,9 +110,11 @@ class NotificationService @@ -110,9 +110,11 @@ class NotificationService
110 else 110 else
111 opts.merge!(noteable_id: note.noteable_id) 111 opts.merge!(noteable_id: note.noteable_id)
112 target = note.noteable 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 end 118 end
117 119
118 # Get users who left comment in thread 120 # Get users who left comment in thread
@@ -190,7 +192,12 @@ class NotificationService @@ -190,7 +192,12 @@ class NotificationService
190 end 192 end
191 193
192 def new_resource_email(target, method) 194 def new_resource_email(target, method)
193 - recipients = reject_muted_users([target.assignee], target.project) 195 + if target.respond_to?(:participants)
  196 + recipients = target.participants
  197 + else
  198 + recipients = []
  199 + end
  200 + recipients = reject_muted_users(recipients, target.project)
194 recipients = recipients.concat(project_watchers(target.project)).uniq 201 recipients = recipients.concat(project_watchers(target.project)).uniq
195 recipients.delete(target.author) 202 recipients.delete(target.author)
196 203
app/views/projects/issues/show.html.haml
@@ -65,4 +65,9 @@ @@ -65,4 +65,9 @@
65 - else 65 - else
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" 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 .voting_notes#notes= render "projects/notes/notes_with_form" 73 .voting_notes#notes= render "projects/notes/notes_with_form"
db/fixtures/development/09_issues.rb
@@ -11,6 +11,7 @@ Gitlab::Seeder.quiet do @@ -11,6 +11,7 @@ Gitlab::Seeder.quiet do
11 next unless user 11 next unless user
12 12
13 user_id = user.id 13 user_id = user.id
  14 + Thread.current[:current_user] = user
14 15
15 Issue.seed(:id, [{ 16 Issue.seed(:id, [{
16 id: i, 17 id: i,
db/fixtures/development/10_merge_requests.rb
@@ -17,6 +17,8 @@ Gitlab::Seeder.quiet do @@ -17,6 +17,8 @@ Gitlab::Seeder.quiet do
17 next if branches.uniq.size < 2 17 next if branches.uniq.size < 2
18 18
19 user_id = user.id 19 user_id = user.id
  20 + Thread.current[:current_user] = user
  21 +
20 MergeRequest.seed(:id, [{ 22 MergeRequest.seed(:id, [{
21 id: i, 23 id: i,
22 source_branch: branches.first, 24 source_branch: branches.first,
doc/update/5.0-to-5.1.md
@@ -41,6 +41,7 @@ sudo -u git -H cp config/puma.rb.example config/puma.rb @@ -41,6 +41,7 @@ sudo -u git -H cp config/puma.rb.example config/puma.rb
41 sudo -u git -H bundle install --without development test postgres --deployment 41 sudo -u git -H bundle install --without development test postgres --deployment
42 sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production 42 sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
43 sudo -u git -H bundle exec rake migrate_merge_requests RAILS_ENV=production 43 sudo -u git -H bundle exec rake migrate_merge_requests RAILS_ENV=production
  44 +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
44 ``` 45 ```
45 46
46 ### 5. Update init.d script with a new one 47 ### 5. Update init.d script with a new one
doc/update/5.2-to-5.3.md
@@ -38,8 +38,8 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production @@ -38,8 +38,8 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
38 38
39 ### 4. Update config files 39 ### 4. Update config files
40 40
41 -* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/gitlab.yml.example but with your settings.  
42 -* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/puma.rb.example but with your settings. 41 +* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/gitlab.yml.example but with your settings.
  42 +* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/puma.rb.example but with your settings.
43 43
44 ### 5. Update Init script 44 ### 5. Update Init script
45 45
spec/services/notification_service_spec.rb
@@ -19,7 +19,7 @@ describe NotificationService do @@ -19,7 +19,7 @@ describe NotificationService do
19 describe 'Notes' do 19 describe 'Notes' do
20 context 'issue note' do 20 context 'issue note' do
21 let(:issue) { create(:issue, assignee: create(:user)) } 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 before do 24 before do
25 build_team(note.project) 25 build_team(note.project)
@@ -30,6 +30,7 @@ describe NotificationService do @@ -30,6 +30,7 @@ describe NotificationService do
30 should_email(@u_watcher.id) 30 should_email(@u_watcher.id)
31 should_email(note.noteable.author_id) 31 should_email(note.noteable.author_id)
32 should_email(note.noteable.assignee_id) 32 should_email(note.noteable.assignee_id)
  33 + should_email(@u_mentioned.id)
33 should_not_email(note.author_id) 34 should_not_email(note.author_id)
34 should_not_email(@u_participating.id) 35 should_not_email(@u_participating.id)
35 should_not_email(@u_disabled.id) 36 should_not_email(@u_disabled.id)
@@ -235,9 +236,11 @@ describe NotificationService do @@ -235,9 +236,11 @@ describe NotificationService do
235 @u_watcher = create(:user, notification_level: Notification::N_WATCH) 236 @u_watcher = create(:user, notification_level: Notification::N_WATCH)
236 @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) 237 @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)
237 @u_disabled = create(:user, notification_level: Notification::N_DISABLED) 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 project.team << [@u_watcher, :master] 241 project.team << [@u_watcher, :master]
240 project.team << [@u_participating, :master] 242 project.team << [@u_participating, :master]
241 project.team << [@u_disabled, :master] 243 project.team << [@u_disabled, :master]
  244 + project.team << [@u_mentioned, :master]
242 end 245 end
243 end 246 end