1# frozen_string_literal: true
2
3module Issues
4  class CreateService < Issues::BaseService
5    include ResolveDiscussions
6    prepend RateLimitedService
7
8    rate_limit key: :issues_create,
9               opts: { scope: [:project, :current_user, :external_author] }
10
11    # NOTE: For Issues::CreateService, we require the spam_params and do not default it to nil, because
12    # spam_checking is likely to be necessary.  However, if there is not a request available in scope
13    # in the caller (for example, an issue created via email) and the required arguments to the
14    # SpamParams constructor are not otherwise available, spam_params: must be explicitly passed as nil.
15    def initialize(project:, current_user: nil, params: {}, spam_params:)
16      super(project: project, current_user: current_user, params: params)
17      @spam_params = spam_params
18    end
19
20    def execute(skip_system_notes: false)
21      @issue = BuildService.new(project: project, current_user: current_user, params: params).execute
22
23      filter_resolve_discussion_params
24
25      create(@issue, skip_system_notes: skip_system_notes)
26    end
27
28    def external_author
29      params[:external_author] # present when creating an issue using service desk (email: from)
30    end
31
32    def before_create(issue)
33      Spam::SpamActionService.new(
34        spammable: issue,
35        spam_params: spam_params,
36        user: current_user,
37        action: :create
38      ).execute
39
40      # current_user (defined in BaseService) is not available within run_after_commit block
41      user = current_user
42      issue.run_after_commit do
43        NewIssueWorker.perform_async(issue.id, user.id)
44        Issues::PlacementWorker.perform_async(nil, issue.project_id)
45        Namespaces::OnboardingIssueCreatedWorker.perform_async(issue.namespace.id)
46      end
47    end
48
49    # Add new items to Issues::AfterCreateService if they can be performed in Sidekiq
50    def after_create(issue)
51      user_agent_detail_service.create
52      resolve_discussions_with_issue(issue)
53      create_escalation_status(issue)
54
55      super
56    end
57
58    def handle_changes(issue, options)
59      super
60      old_associations = options.fetch(:old_associations, {})
61      old_assignees = old_associations.fetch(:assignees, [])
62
63      handle_assignee_changes(issue, old_assignees)
64    end
65
66    def handle_assignee_changes(issue, old_assignees)
67      return if issue.assignees == old_assignees
68
69      create_assignee_note(issue, old_assignees)
70    end
71
72    def resolve_discussions_with_issue(issue)
73      return if discussions_to_resolve.empty?
74
75      Discussions::ResolveService.new(project, current_user,
76                                      one_or_more_discussions: discussions_to_resolve,
77                                      follow_up_issue: issue).execute
78    end
79
80    private
81
82    attr_reader :spam_params
83
84    def create_escalation_status(issue)
85      ::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation?
86    end
87
88    def user_agent_detail_service
89      UserAgentDetailService.new(spammable: @issue, spam_params: spam_params)
90    end
91  end
92end
93
94Issues::CreateService.prepend_mod
95