Commit 40e4d64c authored by Jarka Košanová's avatar Jarka Košanová

Add custom email templates for service desk

- use .gitlab/service_desk_templates as the base dir
- thank_you.md,. new_note.md names required
parent 96c35ddb
...@@ -81,6 +81,33 @@ navigation's **Issues** menu. ...@@ -81,6 +81,33 @@ navigation's **Issues** menu.
![Service Desk Navigation Item](img/service_desk_nav_item.png) ![Service Desk Navigation Item](img/service_desk_nav_item.png)
### Using customized email templates
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/2460) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.7.
When a user submits a new issue using Service Desk, or when a new note is created on a Service Desk issue, an email is sent to the author.
The body of these email messages can customized by using templates. To create a new customized template,
create a new Markdown (`.md`) file inside the `.gitlab/service_desk_templates/`
directory in your repository. Commit and push to your default branch.
#### Thank you email
The **Thank you email** is the email sent to a user after they submit an issue.
The file name of the template has to be `thank_you.md`.
You can use `%{ISSUE_ID}` placeholder which will be replaced by an issue iid in the email and
`%{ISSUE_PATH}` placeholder which will be replaced by project path and the issue iid.
As the service desk issues are created as confidential (only project members can see them)
the response email doesn't provide the issue link.
#### New note email
The **New note email** is the email sent to a user when the issue they submitted has a new comment.
The file name of the template has to be `new_note.md`.
You can use `%{ISSUE_ID}` placeholder which will be replaced by an issue iid
in the email, `%{ISSUE_PATH}` placeholder which will be replaced by
project path and the issue iid and `%{NOTE_TEXT}` placeholder which will be replaced by the note text.
## Using Service Desk ## Using Service Desk
### As an end user (issue creator) ### As an end user (issue creator)
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Emails module Emails
module ServiceDesk module ServiceDesk
extend ActiveSupport::Concern extend ActiveSupport::Concern
include MarkupHelper
included do included do
layout 'service_desk', only: [:service_desk_thank_you_email, :service_desk_new_note_email] layout 'service_desk', only: [:service_desk_thank_you_email, :service_desk_new_note_email]
...@@ -11,11 +12,13 @@ module Emails ...@@ -11,11 +12,13 @@ module Emails
def service_desk_thank_you_email(issue_id) def service_desk_thank_you_email(issue_id)
setup_service_desk_mail(issue_id) setup_service_desk_mail(issue_id)
options = { email_sender = sender(
from: sender(@support_bot.id, send_from_user_email: false, sender_name: @project.service_desk_setting&.outgoing_name), @support_bot.id,
to: @issue.service_desk_reply_to, send_from_user_email: false,
subject: "Re: #{@issue.title} (##{@issue.iid})" sender_name: @project.service_desk_setting&.outgoing_name
} )
options = service_desk_options(email_sender, 'thank_you')
.merge(subject: "Re: #{subject_base}")
mail_new_thread(@issue, options) mail_new_thread(@issue, options)
end end
...@@ -24,11 +27,9 @@ module Emails ...@@ -24,11 +27,9 @@ module Emails
@note = Note.find(note_id) @note = Note.find(note_id)
setup_service_desk_mail(issue_id) setup_service_desk_mail(issue_id)
options = { email_sender = sender(@note.author_id)
from: sender(@note.author_id), options = service_desk_options(email_sender, 'new_note')
to: @issue.service_desk_reply_to, .merge(subject: subject_base)
subject: "#{@issue.title} (##{@issue.iid})"
}
mail_answer_thread(@issue, options) mail_answer_thread(@issue, options)
end end
...@@ -42,5 +43,50 @@ module Emails ...@@ -42,5 +43,50 @@ module Emails
@sent_notification = SentNotification.record(@issue, @support_bot.id, reply_key) @sent_notification = SentNotification.record(@issue, @support_bot.id, reply_key)
end end
def service_desk_options(email_sender, email_type)
{
from: email_sender,
to: @issue.service_desk_reply_to
}.tap do |options|
next unless template_body = template_content(email_type)
options[:body] = template_body
options[:content_type] = 'text/html'
end
end
def template_content(email_type)
template = Gitlab::Template::ServiceDeskTemplate.find(email_type, @project)
text = substitute_template_replacements(template.content)
markdown(text, project: @project)
rescue Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
nil
end
def substitute_template_replacements(template_body)
template_body
.gsub(/%\{\s*ISSUE_ID\s*\}/, issue_id)
.gsub(/%\{\s*ISSUE_PATH\s*\}/, issue_path)
.gsub(/%\{\s*NOTE_TEXT\s*\}/, note_text)
end
def issue_id
"#{Issue.reference_prefix}#{@issue.iid}"
end
def issue_path
@issue.to_reference(full: true)
end
def note_text
@note&.note.to_s
end
def subject_base
"#{@issue.title} (##{@issue.iid})"
end
end end
end end
---
title: Add support for custom email templates for service desk
merge_request: 21745
author:
type: added
# frozen_string_literal: true
module Gitlab
module Template
class ServiceDeskTemplate < BaseTemplate
class << self
def extension
'.md'
end
def base_dir
'.gitlab/service_desk_templates/'
end
def finder(project)
Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require 'email_spec'
describe Emails::ServiceDesk do
include EmailSpec::Helpers
include EmailSpec::Matchers
include EmailHelpers
include_context 'gitlab email notification'
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let(:template) { double(content: template_content) }
class ServiceEmailClass < BaseMailer
include GitlabRoutingHelper
include EmailsHelper
include Emails::ServiceDesk
helper GitlabRoutingHelper
helper EmailsHelper
append_view_path Rails.root.join('ee', 'app', 'views', 'notify')
# this method is implemented in Notify class, we don't need to test it
def reply_key
'test-key'
end
# this method is implemented in Notify class, we don't need to test it
def sender(author_id, params = {})
author_id
end
# this method is implemented in Notify class
#
# We do not need to test the Notify method, it is already tested in notify_spec
def mail_new_thread(issue, options)
# we need to rewrite this in order to look up templates in the correct directory
self.class.mailer_name = 'notify'
# this is needed for default layout
@unsubscribe_url = 'http://unsubscribe.example.com'
mail(options)
end
alias_method :mail_answer_thread, :mail_new_thread
end
shared_examples 'handle template content' do |template_key|
before do
expect(Gitlab::Template::ServiceDeskTemplate).to receive(:find)
.with(template_key, issue.project)
.and_return(template)
end
it 'builds the email correctly' do
aggregate_failures do
is_expected.to have_referable_subject(issue, include_project: false, reply: reply_in_subject)
is_expected.to have_body_text(expected_body)
expect(subject.content_type).to include('text/html')
end
end
end
shared_examples 'read template from repository' do |template_key|
let(:template_content) { 'custom text' }
let(:issue) { create(:issue, project: project)}
context 'when a template is in the repository' do
let(:project) { create(:project, :custom_repo, files: { ".gitlab/service_desk_templates/#{template_key}.md" => template_content }) }
it 'uses the text template from the template' do
is_expected.to have_body_text(template_content)
end
end
context 'when the service_desk_templates directory does not contain correct template' do
let(:project) { create(:project, :custom_repo, files: { ".gitlab/service_desk_templates/another_file.md" => template_content }) }
it 'uses the default template' do
is_expected.to have_body_text(default_text)
end
end
context 'when the service_desk_templates directory does not exist' do
let(:project) { create(:project, :custom_repo, files: { "other_directory/another_file.md" => template_content }) }
it 'uses the default template' do
is_expected.to have_body_text(default_text)
end
end
context 'when the project does not have a repo' do
let(:project) { create(:project) }
it 'uses the default template' do
is_expected.to have_body_text(default_text)
end
end
end
describe '.service_desk_thank_you_email' do
let_it_be(:reply_in_subject) { true }
let_it_be(:default_text) do
"Thank you for your support request! We are tracking your request as ticket #{issue.to_reference}, and will respond as soon as we can."
end
subject { ServiceEmailClass.service_desk_thank_you_email(issue.id) }
it_behaves_like 'read template from repository', 'thank_you'
context 'handling template markdown' do
context 'with a simple text' do
let(:template_content) { 'thank you, **your new issue** has been created.' }
let(:expected_body) { 'thank you, <strong>your new issue</strong> has been created.' }
it_behaves_like 'handle template content', 'thank_you'
end
context 'with an issue id and issue path placeholders' do
let(:template_content) { 'thank you, **your new issue:** %{ISSUE_ID}, path: %{ISSUE_PATH}' }
let(:expected_body) { "thank you, <strong>your new issue:</strong> ##{issue.iid}, path: #{project.full_path}##{issue.iid}" }
it_behaves_like 'handle template content', 'thank_you'
end
context 'with an issue id placeholder with whitespace' do
let(:template_content) { 'thank you, **your new issue:** %{ ISSUE_ID}' }
let(:expected_body) { "thank you, <strong>your new issue:</strong> ##{issue.iid}" }
it_behaves_like 'handle template content', 'thank_you'
end
context 'with unexpected placeholder' do
let(:template_content) { 'thank you, **your new issue:** %{this is issue}' }
let(:expected_body) { "thank you, <strong>your new issue:</strong> %{this is issue}" }
it_behaves_like 'handle template content', 'thank_you'
end
end
end
describe '.service_desk_new_note_email' do
let_it_be(:reply_in_subject) { false }
let_it_be(:note) { create(:note_on_issue, noteable: issue, project: project) }
let_it_be(:default_text) { note.note }
subject { ServiceEmailClass.service_desk_new_note_email(issue.id, note.id) }
it_behaves_like 'read template from repository', 'new_note'
context 'handling template markdown' do
context 'with a simple text' do
let(:template_content) { 'thank you, **new note on issue** has been created.' }
let(:expected_body) { 'thank you, <strong>new note on issue</strong> has been created.' }
it_behaves_like 'handle template content', 'new_note'
end
context 'with an issue id, issue path and note placeholders' do
let(:template_content) { 'thank you, **new note on issue:** %{ISSUE_ID}, path: %{ISSUE_PATH}: %{NOTE_TEXT}' }
let(:expected_body) { "thank you, <strong>new note on issue:</strong> ##{issue.iid}, path: #{project.full_path}##{issue.iid}: #{note.note}" }
it_behaves_like 'handle template content', 'new_note'
end
context 'with an issue id placeholder with whitespace' do
let(:template_content) { 'thank you, **new note on issue:** %{ ISSUE_ID}: %{ NOTE_TEXT }' }
let(:expected_body) { "thank you, <strong>new note on issue:</strong> ##{issue.iid}: #{note.note}" }
it_behaves_like 'handle template content', 'new_note'
end
context 'with unexpected placeholder' do
let(:template_content) { 'thank you, **new note on issue:** %{this is issue}' }
let(:expected_body) { "thank you, <strong>new note on issue:</strong> %{this is issue}" }
it_behaves_like 'handle template content', 'new_note'
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment