diff options
Diffstat (limited to 'lib/gitlab/email/handler/service_desk_handler.rb')
-rw-r--r-- | lib/gitlab/email/handler/service_desk_handler.rb | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/lib/gitlab/email/handler/service_desk_handler.rb b/lib/gitlab/email/handler/service_desk_handler.rb new file mode 100644 index 00000000000..bcd8b98a06f --- /dev/null +++ b/lib/gitlab/email/handler/service_desk_handler.rb @@ -0,0 +1,152 @@ +# 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) + super(mail, mail_key) + + if service_desk_key.present? + @service_desk_key = service_desk_key + elsif !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s)) + @project_slug = matched[:project_slug] + @project_id = matched[:project_id]&.to_i + elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s) + @project_path = matched[:project_path] + end + 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! + send_thank_you_email! if from_address + end + + def metrics_params + super.merge(project: project&.full_path) + end + + def metrics_event + :receive_email_service_desk + end + + private + + attr_reader :project_id, :project_path, :service_desk_key + + def project + strong_memoize(:project) do + @project = service_desk_key ? project_from_key : super + @project = nil unless @project&.service_desk_enabled? + @project + end + end + + def project_from_key + return unless match = service_desk_key.match(PROJECT_KEY_PATTERN) + + project = Project.find_by_service_desk_project_key(match[:key]) + return unless valid_project_key?(project, match[:slug]) + + project + end + + def valid_project_key?(project, slug) + project.present? && slug == project.full_path_slug && Feature.enabled?(:service_desk_custom_address, project) + end + + def create_issue! + @issue = Issues::CreateService.new( + project, + User.support_bot, + title: issue_title, + description: message_including_template, + confidential: true, + service_desk_reply_to: from_address + ).execute + + raise InvalidIssueError unless @issue.persisted? + + if service_desk_setting&.issue_template_missing? + create_template_not_found_note(@issue) + end + end + + def send_thank_you_email! + Notify.service_desk_thank_you_email(@issue.id).deliver_later! + end + + def message_including_template + description = message_including_reply + 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) + 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 + + note_params = { + noteable: issue, + note: warning_note + } + + ::Notes::CreateService.new( + project, + User.support_bot, + note_params + ).execute + end + + def from_address + (mail.reply_to || []).first || mail.from.first || mail.sender + end + + def issue_title + from = "(from #{from_address})" if from_address + + "Service Desk #{from}: #{mail.subject}" + end + + def can_handle_legacy_format? + project_path && project_path.include?('/') && !mail_key.include?('+') + end + + def author + User.support_bot + end + end + end + end +end |