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
|
# frozen_string_literal: true
module IssuableLinks
class CreateService < BaseService
attr_reader :issuable, :current_user, :params
def initialize(issuable, user, params)
@issuable = issuable
@current_user = user
@params = params.dup
@errors = []
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
references = create_links
if @errors.present?
return error(@errors.join('. '), 422)
end
track_event
success(created_references: references)
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(link)
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.map do |referenced_object|
link = relate_issuables(referenced_object)
if link.valid?
after_create_for(link)
else
@errors << _("%{ref} cannot be added: %{error}") % {
ref: referenced_object.to_reference,
error: link.errors.messages.values.flatten.to_sentence
}
end
link
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(issuable_link)
SystemNoteService.relate_issuable(issuable_link.source, issuable_link.target, current_user)
SystemNoteService.relate_issuable(issuable_link.target, issuable_link.source, 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
# Override on child classes to perform
# actions when the service is executed.
def track_event
# no-op
end
# Override on child classes to
# perform actions for each object created.
def after_create_for(_link)
# no-op
end
end
end
IssuableLinks::CreateService.prepend_mod_with('IssuableLinks::CreateService')
|