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
|
# frozen_string_literal: true
module QuickActions
class InterpretService < BaseService
include Gitlab::Utils::StrongMemoize
include Gitlab::QuickActions::Dsl
include Gitlab::QuickActions::IssueActions
include Gitlab::QuickActions::IssuableActions
include Gitlab::QuickActions::IssueAndMergeRequestActions
include Gitlab::QuickActions::MergeRequestActions
include Gitlab::QuickActions::CommitActions
include Gitlab::QuickActions::CommonActions
include Gitlab::QuickActions::RelateActions
attr_reader :quick_action_target
# Counts how many commands have been executed.
# Used to display relevant feedback on UI when a note
# with only commands has been processed.
attr_accessor :commands_executed_count
# Takes an quick_action_target and returns an array of all the available commands
# represented with .to_h
def available_commands(quick_action_target)
@quick_action_target = quick_action_target
self.class.command_definitions.map do |definition|
next unless definition.available?(self)
definition.to_h(self)
end.compact
end
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, a hash of changes to be applied to a record
# and a string containing the execution_message to show to the user.
def execute(content, quick_action_target, only: nil)
return [content, {}, ''] unless current_user.can?(:use_quick_actions)
@quick_action_target = quick_action_target
@updates = {}
@execution_message = {}
content, commands = extractor.extract_commands(content, only: only)
extract_updates(commands)
[content, @updates, execution_messages_for(commands)]
end
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and array of changes explained.
def explain(content, quick_action_target)
return [content, []] unless current_user.can?(:use_quick_actions)
@quick_action_target = quick_action_target
content, commands = extractor.extract_commands(content)
commands = explain_commands(commands)
[content, commands]
end
private
def extractor
Gitlab::QuickActions::Extractor.new(self.class.command_definitions)
end
# rubocop: disable CodeReuse/ActiveRecord
def extract_users(params)
return [] if params.nil?
users = extract_references(params, :user)
if users.empty?
users =
if params.strip == 'me'
[current_user]
else
User.where(username: params.split(' ').map(&:strip))
end
end
users
end
# rubocop: enable CodeReuse/ActiveRecord
def find_milestones(project, params = {})
group_ids = project.group.self_and_ancestors.select(:id) if project.group
MilestonesFinder.new(params.merge(project_ids: [project.id], group_ids: group_ids)).execute
end
def parent
project || group
end
def group
strong_memoize(:group) do
quick_action_target.group if quick_action_target.respond_to?(:group)
end
end
def find_labels(labels_params = nil)
extract_references(labels_params, :label) | find_labels_by_name_no_tilde(labels_params)
end
def find_labels_by_name_no_tilde(labels_params)
return Label.none if label_with_tilde?(labels_params)
finder_params = { include_ancestor_groups: true }
finder_params[:project_id] = project.id if project
finder_params[:group_id] = group.id if group
finder_params[:name] = extract_label_names(labels_params) if labels_params
LabelsFinder.new(current_user, finder_params).execute
end
def label_with_tilde?(labels_params)
labels_params&.include?('~')
end
def extract_label_names(labels_params)
# '"A" "A B C" A B' => ["A", "A B C", "A", "B"]
labels_params.scan(/"([^"]+)"|([^ ]+)/).flatten.compact
end
def find_label_references(labels_param, format = :id)
labels_to_reference(find_labels(labels_param), format)
end
def labels_to_reference(labels, format = :id)
labels.map { |l| l.to_reference(format: format) }
end
def find_label_ids(labels_param)
find_labels(labels_param).map(&:id)
end
def explain_commands(commands)
map_commands(commands, :explain)
end
def execution_messages_for(commands)
map_commands(commands, :execute_message).join(' ')
end
def map_commands(commands, method)
commands.map do |name, arg|
definition = self.class.definition_by_name(name)
next unless definition
case method
when :explain
definition.explain(self, arg)
when :execute_message
@execution_message[name.to_sym] || definition.execute_message(self, arg)
end
end.compact
end
def extract_updates(commands)
commands.each do |name, arg|
definition = self.class.definition_by_name(name)
next unless definition
definition.execute(self, arg)
end
end
# rubocop: disable CodeReuse/ActiveRecord
def extract_references(arg, type)
return [] unless arg
ext = Gitlab::ReferenceExtractor.new(project, current_user)
ext.analyze(arg, author: current_user, group: group)
ext.references(type)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
QuickActions::InterpretService.prepend_if_ee('EE::QuickActions::InterpretService')
|