summaryrefslogtreecommitdiff
path: root/app/services/issuable_links/create_service.rb
blob: 802260c8fae56e0e3f0586d6192fc9227fc00496 (plain)
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
# frozen_string_literal: true

module IssuableLinks
  class CreateService < BaseService
    include IncidentManagement::UsageData

    attr_reader :issuable, :current_user, :params

    def initialize(issuable, user, params)
      @issuable = issuable
      @current_user = user
      @params = params.dup
    end

    def execute
      # If ALL referenced issues are already assigned to the given epic it renders a conflict status,
      # otherwise create issue links for the issues which
      # are still not assigned and return success message.
      if render_conflict_error?
        return error(issuables_already_assigned_message, 409)
      end

      if render_not_found_error?
        return error(issuables_not_found_message, 404)
      end

      @errors = []
      create_links

      if @errors.present?
        return error(@errors.join('. '), 422)
      end

      track_event

      success
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def relate_issuables(referenced_issuable)
      link = link_class.find_or_initialize_by(source: issuable, target: referenced_issuable)

      set_link_type(link)

      if link.changed? && link.save
        create_notes(referenced_issuable)
      end

      link
    end
    # rubocop: enable CodeReuse/ActiveRecord

    private

    def render_conflict_error?
      referenced_issuables.present? && (referenced_issuables - previous_related_issuables).empty?
    end

    def render_not_found_error?
      linkable_issuables(referenced_issuables).empty?
    end

    def create_links
      objects = linkable_issuables(referenced_issuables)
      link_issuables(objects)
    end

    def link_issuables(target_issuables)
      target_issuables.each do |referenced_object|
        link = relate_issuables(referenced_object)

        unless link.valid?
          @errors << _("%{ref} cannot be added: %{error}") % {
            ref: referenced_object.to_reference,
            error: link.errors.messages.values.flatten.to_sentence
          }
        end
      end
    end

    def referenced_issuables
      @referenced_issuables ||= begin
        target_issuable = params[:target_issuable]

        if params[:issuable_references].present?
          extract_references
        elsif target_issuable
          [target_issuable]
        else
          []
        end
      end
    end

    def extract_references
      issuable_references = params[:issuable_references]
      text = issuable_references.join(' ')

      extractor = Gitlab::ReferenceExtractor.new(issuable.project, current_user)
      extractor.analyze(text, extractor_context)

      references(extractor)
    end

    def references(extractor)
      extractor.issues
    end

    def extractor_context
      {}
    end

    def issuables_already_assigned_message
      _('%{issuable}(s) already assigned' % { issuable: target_issuable_type.capitalize })
    end

    def issuables_not_found_message
      _('No matching %{issuable} found. Make sure that you are adding a valid %{issuable} URL.' % { issuable: target_issuable_type })
    end

    def target_issuable_type
      :issue
    end

    def create_notes(referenced_issuable)
      SystemNoteService.relate_issuable(issuable, referenced_issuable, current_user)
      SystemNoteService.relate_issuable(referenced_issuable, issuable, current_user)
    end

    def linkable_issuables(objects)
      raise NotImplementedError
    end

    def previous_related_issuables
      raise NotImplementedError
    end

    def link_class
      raise NotImplementedError
    end

    def set_link_type(_link)
      # no-op
    end
  end
end

IssuableLinks::CreateService.prepend_mod_with('IssuableLinks::CreateService')