1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
# frozen_string_literal: true
# handles service desk issue creation emails with these formats:
# incoming+gitlab-org-gitlab-ce-20-issue-@incoming.gitlab.com
# incoming+gitlab-org/gitlab-ce@incoming.gitlab.com (legacy)
module Gitlab
module Email
module Handler
class ServiceDeskHandler < BaseHandler
include ReplyProcessing
include Gitlab::Utils::StrongMemoize
HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-issue-\z/.freeze
HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\z/.freeze
PROJECT_KEY_PATTERN = /\A(?<slug>.+)-(?<key>[a-z0-9_]+)\z/.freeze
def initialize(mail, mail_key, service_desk_key: nil)
if service_desk_key
mail_key ||= service_desk_key
@service_desk_key = service_desk_key
end
super(mail, mail_key)
match_project_slug || match_legacy_project_slug
end
def can_handle?
Gitlab::ServiceDesk.supported? && (project_id || can_handle_legacy_format? || service_desk_key)
end
def execute
raise ProjectNotFound if project.nil?
create_issue_or_note
if from_address
add_email_participant
send_thank_you_email unless reply_email?
end
end
def match_project_slug
return if mail_key&.include?('/')
return unless matched = HANDLER_REGEX.match(mail_key.to_s)
@project_slug = matched[:project_slug]
@project_id = matched[:project_id]&.to_i
end
def match_legacy_project_slug
return unless matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s)
@project_path = matched[:project_path]
end
def metrics_event
:receive_email_service_desk
end
def project
strong_memoize(:project) do
project_record = super
project_record ||= project_from_key if service_desk_key
project_record&.service_desk_enabled? ? project_record : nil
end
end
private
attr_reader :project_id, :project_path, :service_desk_key
def project_from_key
return unless match = service_desk_key.match(PROJECT_KEY_PATTERN)
Project.with_service_desk_key(match[:key]).find do |project|
valid_project_key?(project, match[:slug])
end
end
def valid_project_key?(project, slug)
project.present? && slug == project.full_path_slug
end
def create_issue_or_note
if reply_email?
create_note_from_reply_email
else
create_issue!
end
end
def create_issue!
result = ::Issues::CreateService.new(
project: project,
current_user: User.support_bot,
params: {
title: mail.subject,
description: message_including_template,
confidential: true,
external_author: from_address,
extra_params: {
cc: mail.cc
}
},
spam_params: nil
).execute
raise InvalidIssueError if result.error?
@issue = result[:issue]
begin
::Issue::Email.create!(issue: @issue, email_message_id: mail.message_id)
rescue StandardError => e
Gitlab::ErrorTracking.log_exception(e)
end
if service_desk_setting&.issue_template_missing?
create_template_not_found_note
end
end
def issue_from_reply_to
strong_memoize(:issue_from_reply_to) do
next unless mail.in_reply_to
Issue::Email.find_by_email_message_id(mail.in_reply_to)&.issue
end
end
def reply_email?
issue_from_reply_to.present?
end
def create_note_from_reply_email
@issue = issue_from_reply_to
create_note(message_including_reply)
end
def send_thank_you_email
Notify.service_desk_thank_you_email(@issue.id).deliver_later
Gitlab::Metrics::BackgroundTransaction.current&.add_event(:service_desk_thank_you_email)
end
def message_including_template
description = message_including_reply_or_only_quotes
template_content = service_desk_setting&.issue_template_content
if template_content.present?
description += " \n" + template_content
end
description
end
def service_desk_setting
strong_memoize(:service_desk_setting) do
project.service_desk_setting
end
end
def create_template_not_found_note
issue_template_key = service_desk_setting&.issue_template_key
warning_note = <<-MD.strip_heredoc
WARNING: The template file #{issue_template_key}.md used for service desk issues is empty or could not be found.
Please check service desk settings and update the file to be used.
MD
create_note(warning_note)
end
def create_note(note)
::Notes::CreateService.new(
project,
User.support_bot,
noteable: @issue,
note: note
).execute
end
def from_address
(mail.reply_to || []).first || mail.from.first || mail.sender
end
def can_handle_legacy_format?
project_path && project_path.include?('/') && !mail_key.include?('+')
end
def author
User.support_bot
end
def add_email_participant
return if reply_email? && !Feature.enabled?(:issue_email_participants, @issue.project)
@issue.issue_email_participants.create(email: from_address)
end
end
end
end
end
|