Commit c4f9dff480ea030d6878b0197a71670f3a141e0e

Authored by Dmitriy Zaporozhets
2 parents dcda7516 ad278c4e

Merge branch 'simplify-emails-content' into 'master'

Streamline the content of notification emails

In notification emails, the actual content of the email is often buried under several blocks of chrome — and may even be truncated or completely missing. Ideally, the notification emails would be like *real emails*: a short message of meaningful text, sent from the author of the change that triggered the notification.

This MR includes the following changes to notification emails:

* Remove much of the chrome (e.g. the "GitLab" header)
* Emphasize the content (no more small, grayed-out content)
* Add missing informations to the emails (issue description in "new issue" email, file name in "diff comment" email)
* Add a consistent "View in GitLab" link in the footer
* The assignee is displayed only if someone is assigned
* Fix a rendering bug when viewing emails with [Zimbra](http://www.zimbra.com/)

We use these patches at [Capitaine Train](http://www.capitainetrain.com), and it has been a surprisingly big productivity boost for us.

![Before and after](http://f.cl.ly/items/3n0P2c2v1P0y011c0D3e/Before%20and%20After.png)
app/mailers/emails/groups.rb
... ... @@ -3,7 +3,7 @@ module Emails
3 3 def group_access_granted_email(user_group_id)
4 4 @membership = UsersGroup.find(user_group_id)
5 5 @group = @membership.group
6   -
  6 + @target_url = group_url(@group)
7 7 mail(to: @membership.user.email,
8 8 subject: subject("Access to group was granted"))
9 9 end
... ...
app/mailers/emails/issues.rb
... ... @@ -3,6 +3,7 @@ module Emails
3 3 def new_issue_email(recipient_id, issue_id)
4 4 @issue = Issue.find(issue_id)
5 5 @project = @issue.project
  6 + @target_url = project_issue_url(@project, @issue)
6 7 mail(from: sender(@issue.author_id),
7 8 to: recipient(recipient_id),
8 9 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ... @@ -12,6 +13,7 @@ module Emails
12 13 @issue = Issue.find(issue_id)
13 14 @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
14 15 @project = @issue.project
  16 + @target_url = project_issue_url(@project, @issue)
15 17 mail(from: sender(updated_by_user_id),
16 18 to: recipient(recipient_id),
17 19 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ... @@ -21,6 +23,7 @@ module Emails
21 23 @issue = Issue.find issue_id
22 24 @project = @issue.project
23 25 @updated_by = User.find updated_by_user_id
  26 + @target_url = project_issue_url(@project, @issue)
24 27 mail(from: sender(updated_by_user_id),
25 28 to: recipient(recipient_id),
26 29 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ... @@ -31,6 +34,7 @@ module Emails
31 34 @issue_status = status
32 35 @project = @issue.project
33 36 @updated_by = User.find updated_by_user_id
  37 + @target_url = project_issue_url(@project, @issue)
34 38 mail(from: sender(updated_by_user_id),
35 39 to: recipient(recipient_id),
36 40 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ...
app/mailers/emails/merge_requests.rb
... ... @@ -3,6 +3,7 @@ module Emails
3 3 def new_merge_request_email(recipient_id, merge_request_id)
4 4 @merge_request = MergeRequest.find(merge_request_id)
5 5 @project = @merge_request.project
  6 + @target_url = project_merge_request_url(@project, @merge_request)
6 7 mail(from: sender(@merge_request.author_id),
7 8 to: recipient(recipient_id),
8 9 subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
... ... @@ -12,6 +13,7 @@ module Emails
12 13 @merge_request = MergeRequest.find(merge_request_id)
13 14 @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
14 15 @project = @merge_request.project
  16 + @target_url = project_merge_request_url(@project, @merge_request)
15 17 mail(from: sender(updated_by_user_id),
16 18 to: recipient(recipient_id),
17 19 subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
... ... @@ -21,6 +23,7 @@ module Emails
21 23 @merge_request = MergeRequest.find(merge_request_id)
22 24 @updated_by = User.find updated_by_user_id
23 25 @project = @merge_request.project
  26 + @target_url = project_merge_request_url(@project, @merge_request)
24 27 mail(from: sender(updated_by_user_id),
25 28 to: recipient(recipient_id),
26 29 subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
... ... @@ -29,6 +32,7 @@ module Emails
29 32 def merged_merge_request_email(recipient_id, merge_request_id)
30 33 @merge_request = MergeRequest.find(merge_request_id)
31 34 @project = @merge_request.project
  35 + @target_url = project_merge_request_url(@project, @merge_request)
32 36 mail(from: sender(@merge_request.author_id_of_changes),
33 37 to: recipient(recipient_id),
34 38 subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
... ...
app/mailers/emails/notes.rb
... ... @@ -4,6 +4,7 @@ module Emails
4 4 @note = Note.find(note_id)
5 5 @commit = @note.noteable
6 6 @project = @note.project
  7 + @target_url = project_commit_url(@project, @commit, anchor: "note_#{@note.id}")
7 8 mail(from: sender(@note.author_id),
8 9 to: recipient(recipient_id),
9 10 subject: subject("#{@commit.title} (#{@commit.short_id})"))
... ... @@ -13,6 +14,7 @@ module Emails
13 14 @note = Note.find(note_id)
14 15 @issue = @note.noteable
15 16 @project = @note.project
  17 + @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}")
16 18 mail(from: sender(@note.author_id),
17 19 to: recipient(recipient_id),
18 20 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ... @@ -22,6 +24,7 @@ module Emails
22 24 @note = Note.find(note_id)
23 25 @merge_request = @note.noteable
24 26 @project = @note.project
  27 + @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}")
25 28 mail(from: sender(@note.author_id),
26 29 to: recipient(recipient_id),
27 30 subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
... ... @@ -30,6 +33,7 @@ module Emails
30 33 def note_wall_email(recipient_id, note_id)
31 34 @note = Note.find(note_id)
32 35 @project = @note.project
  36 + @target_url = project_wall_url(@note.project, anchor: "note_#{@note.id}")
33 37 mail(from: sender(@note.author_id),
34 38 to: recipient(recipient_id),
35 39 subject: subject("Note on wall"))
... ...
app/mailers/emails/profile.rb
... ... @@ -3,6 +3,7 @@ module Emails
3 3 def new_user_email(user_id, password)
4 4 @user = User.find(user_id)
5 5 @password = password
  6 + @target_url = user_url(@user)
6 7 mail(to: @user.email, subject: subject("Account was created for you"))
7 8 end
8 9  
... ... @@ -15,6 +16,7 @@ module Emails
15 16 def new_ssh_key_email(key_id)
16 17 @key = Key.find(key_id)
17 18 @user = @key.user
  19 + @target_url = user_url(@user)
18 20 mail(to: @user.email, subject: subject("SSH key was added to your account"))
19 21 end
20 22 end
... ...
app/mailers/emails/projects.rb
... ... @@ -3,6 +3,7 @@ module Emails
3 3 def project_access_granted_email(user_project_id)
4 4 @users_project = UsersProject.find user_project_id
5 5 @project = @users_project.project
  6 + @target_url = project_url(@project)
6 7 mail(to: @users_project.user.email,
7 8 subject: subject("Access to project was granted"))
8 9 end
... ... @@ -10,6 +11,7 @@ module Emails
10 11 def project_was_moved_email(project_id, user_id)
11 12 @user = User.find user_id
12 13 @project = Project.find project_id
  14 + @target_url = project_url(@project)
13 15 mail(to: @user.email,
14 16 subject: subject("Project was moved"))
15 17 end
... ... @@ -21,6 +23,11 @@ module Emails
21 23 @commits = Commit.decorate(compare.commits)
22 24 @diffs = compare.diffs
23 25 @branch = branch
  26 + if @commits.length > 1
  27 + @target_url = project_compare_url(@project, from: @commits.first, to: @commits.last)
  28 + else
  29 + @target_url = project_commit_url(@project, @compare.commit)
  30 + end
24 31  
25 32 mail(from: sender(author_id),
26 33 to: recipient,
... ...
app/views/layouts/notify.html.haml
... ... @@ -3,20 +3,24 @@
3 3 %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
4 4 %title
5 5 GitLab
  6 + :css
  7 + p.details {
  8 + font-style:italic;
  9 + color:#777
  10 + }
  11 + .footer p {
  12 + font-size:small;
  13 + color:#777
  14 + }
6 15  
7 16 %body
8   - %h1{style: "background: #EEE; border-bottom: 1px solid #DDD; color: #474D57; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
9   - GitLab
10   - - if @project
11   - \|
12   - = link_to @project.name_with_namespace, project_url(@project), style: 'color: #29B; text-decoration: none'
13   - %table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
14   - %tr
15   - %td{align: "left", style: "margin: 0; padding: 10px;"}
16   - = yield
17   - %br
18   - %tr
19   - %td{align: "left", style: "margin: 0; padding: 10px;"}
20   - %p{style: "font-size:small;color:#777"}
21   - - if @project
22   - You're receiving this notification because you are a member of the #{@project.name_with_namespace} project team.
  17 + %div.content
  18 + = yield
  19 + %div.footer{style: "margin-top: 10px;"}
  20 + %p
  21 + \—
  22 + %br
  23 + - if @project
  24 + You're receiving this notification because you are a member of the #{link_to @project.name_with_namespace, project_url(@project)} project team.
  25 + - if @target_url
  26 + #{link_to "View in GitLab", @target_url}
... ...
app/views/notify/_note_message.html.haml
1   -%p
2   - %strong #{@note.author_name}
3   - wrote:
4   -
5   -%cite{style: 'color: #666'}
  1 +%div
6 2 = markdown(@note.note)
... ...
app/views/notify/closed_issue_email.html.haml
1 1 %p
2 2 = "Issue was closed by #{@updated_by.name}"
3   -%p
4   - = "Issue ##{@issue.iid}"
5   - = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
... ...
app/views/notify/closed_merge_request_email.html.haml
1 1 %p
2   - = "Merge Request #{@merge_request.iid} was closed by #{@updated_by.name}"
3   -%p
4   - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5   -%p
6   - != merge_path_description(@merge_request, '→')
7   -%p
8   - Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
9   -
  2 + = "Merge Request !#{@merge_request.iid} was closed by #{@updated_by.name}"
... ...
app/views/notify/group_access_granted_email.html.haml
1 1 %p
2 2 = "You have been granted #{@membership.human_access} access to group"
3   -%p
4   - = link_to group_url(@group) do
5   - = @group.name
... ...
app/views/notify/issue_status_changed_email.html.haml
1 1 %p
2 2 = "Issue was #{@issue_status} by #{@updated_by.name}"
3   -%p
4   - = "Issue ##{@issue.iid}"
5   - = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
... ...
app/views/notify/merged_merge_request_email.html.haml
1 1 %p
2   - = "Merge Request #{@merge_request.iid} was merged"
3   -%p
4   - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5   -%p
6   - != merge_path_description(@merge_request, '→')
7   -%p
8   - Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
9   -
  2 + = "Merge Request !#{@merge_request.iid} was merged"
... ...
app/views/notify/new_issue_email.html.haml
1   -%p
2   - New Issue was created.
3   -%p
4   - = "Issue ##{@issue.iid}"
5   - = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
6   -%p
7   - Author: #{@issue.author_name}
8   -%p
9   - Assignee: #{@issue.assignee_name}
  1 +-if @issue.description
  2 + = markdown(@issue.description)
  3 +
  4 +- if @issue.assignee_id.present?
  5 + %p
  6 + Assignee: #{@issue.assignee_name}
... ...
app/views/notify/new_merge_request_email.html.haml
1   -%p
2   - = "New Merge Request ##{@merge_request.iid}"
3   -%p
4   - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5   -%p
  1 +%p.details
6 2 != merge_path_description(@merge_request, '→')
7   -%p
8   - Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
9 3  
  4 +- if @merge_request.assignee_id.present?
  5 + %p
  6 + Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
  7 +
  8 +-if @merge_request.description
  9 + = markdown(@merge_request.description)
... ...
app/views/notify/note_commit_email.html.haml
1   -%p
2   - = "New comment for Commit #{@commit.short_id}"
3   - = link_to_gfm truncate(@commit.title, length: 16), project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")
4 1 = render 'note_message'
5 2  
... ...
app/views/notify/note_issue_email.html.haml
1   -%p
2   - = "New comment for Issue ##{@issue.iid}"
3   - = link_to_gfm truncate(@issue.title, length: 35), project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")
4 1 = render 'note_message'
... ...
app/views/notify/note_merge_request_email.html.haml
1   -%p
2   - - if @note.for_diff_line?
3   - = link_to "New comment on diff", diffs_project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
4   - - else
5   - = link_to "New comment", project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
6   - for Merge Request ##{@merge_request.iid}
7   - %cite "#{truncate(@merge_request.title, length: 20)}"
  1 +- if @note.diff_file_name
  2 + %p.details
  3 + New comment on diff for
  4 + = link_to @note.diff_file_name, @target_url
  5 + \:
  6 +
8 7 = render 'note_message'
... ...
app/views/notify/note_wall_email.html.haml
1   -%p
2   - New message on
3   - = link_to "Project Wall", project_wall_url(@note.project, anchor: "note_#{@note.id}")
4   -
5 1 = render 'note_message'
... ...
app/views/notify/reassigned_issue_email.html.haml
1 1 %p
2   - = "Reassigned Issue ##{@issue.iid}"
3   - = link_to_gfm truncate(@issue.title, length: 30), project_issue_url(@issue.project, @issue)
4   -%p
5 2 Assignee changed
6 3 - if @previous_assignee
7 4 from
... ...
app/views/notify/reassigned_merge_request_email.html.haml
1 1 %p
2   - = "Reassigned Merge Request ##{@merge_request.iid}"
3   - = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.target_project, @merge_request)
4   -%p
5 2 Assignee changed
6 3 - if @previous_assignee
7 4 from
... ...
spec/mailers/notify_spec.rb
... ... @@ -146,7 +146,8 @@ describe Notify do
146 146 end
147 147  
148 148 context 'for issues' do
149   - let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project ) }
  149 + let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project) }
  150 + let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: Faker::Lorem.sentence) }
150 151  
151 152 describe 'that are new' do
152 153 subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
... ... @@ -162,6 +163,14 @@ describe Notify do
162 163 end
163 164 end
164 165  
  166 + describe 'that are new with a description' do
  167 + subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) }
  168 +
  169 + it 'contains the description' do
  170 + should have_body_text /#{issue_with_description.description}/
  171 + end
  172 + end
  173 +
165 174 describe 'that have been reassigned' do
166 175 subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) }
167 176  
... ... @@ -221,6 +230,7 @@ describe Notify do
221 230  
222 231 context 'for merge requests' do
223 232 let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) }
  233 + let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: Faker::Lorem.sentence) }
224 234  
225 235 describe 'that are new' do
226 236 subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
... ... @@ -244,6 +254,14 @@ describe Notify do
244 254 end
245 255 end
246 256  
  257 + describe 'that are new with a description' do
  258 + subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
  259 +
  260 + it 'contains the description' do
  261 + should have_body_text /#{merge_request_with_description.description}/
  262 + end
  263 + end
  264 +
247 265 describe 'that are reassigned' do
248 266 subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) }
249 267  
... ... @@ -335,10 +353,6 @@ describe Notify do
335 353 should deliver_to recipient.email
336 354 end
337 355  
338   - it 'contains the name of the note\'s author' do
339   - should have_body_text /#{note_author.name}/
340   - end
341   -
342 356 it 'contains the message from the note' do
343 357 should have_body_text /#{note.note}/
344 358 end
... ... @@ -468,6 +482,8 @@ describe Notify do
468 482 let(:example_site_path) { root_path }
469 483 let(:user) { create(:user) }
470 484 let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, 'cd5c4bac', 'b1e6a9db') }
  485 + let(:commits) { Commit.decorate(compare.commits) }
  486 + let(:diff_path) { project_compare_path(project, from: commits.first, to: commits.last) }
471 487  
472 488 subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) }
473 489  
... ... @@ -492,5 +508,9 @@ describe Notify do
492 508 it 'includes diffs' do
493 509 should have_body_text /Checkout wiki pages for installation information/
494 510 end
  511 +
  512 + it 'contains a link to the diff' do
  513 + should have_body_text /#{diff_path}/
  514 + end
495 515 end
496 516 end
... ...