2018-11-18 11:00:15 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
module QuickActions
|
2016-09-13 17:45:13 +05:30
|
|
|
class InterpretService < BaseService
|
2019-02-15 15:39:39 +05:30
|
|
|
include Gitlab::Utils::StrongMemoize
|
2017-09-10 17:25:29 +05:30
|
|
|
include Gitlab::QuickActions::Dsl
|
2019-07-07 11:18:12 +05:30
|
|
|
include Gitlab::QuickActions::IssueActions
|
|
|
|
include Gitlab::QuickActions::IssuableActions
|
2019-12-04 20:38:33 +05:30
|
|
|
include Gitlab::QuickActions::IssueAndMergeRequestActions
|
2019-07-07 11:18:12 +05:30
|
|
|
include Gitlab::QuickActions::MergeRequestActions
|
|
|
|
include Gitlab::QuickActions::CommitActions
|
|
|
|
include Gitlab::QuickActions::CommonActions
|
2020-11-24 15:15:51 +05:30
|
|
|
include Gitlab::QuickActions::RelateActions
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
attr_reader :quick_action_target
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
# 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
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
# Takes an quick_action_target and returns an array of all the available commands
|
2018-03-27 19:54:05 +05:30
|
|
|
# represented with .to_h
|
2019-07-07 11:18:12 +05:30
|
|
|
def available_commands(quick_action_target)
|
|
|
|
@quick_action_target = quick_action_target
|
2018-03-27 19:54:05 +05:30
|
|
|
|
|
|
|
self.class.command_definitions.map do |definition|
|
|
|
|
next unless definition.available?(self)
|
|
|
|
|
|
|
|
definition.to_h(self)
|
|
|
|
end.compact
|
|
|
|
end
|
|
|
|
|
2016-09-13 17:45:13 +05:30
|
|
|
# Takes a text and interprets the commands that are extracted from it.
|
2019-10-12 21:52:04 +05:30
|
|
|
# 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.
|
2019-07-07 11:18:12 +05:30
|
|
|
def execute(content, quick_action_target, only: nil)
|
2019-10-12 21:52:04 +05:30
|
|
|
return [content, {}, ''] unless current_user.can?(:use_quick_actions)
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
@quick_action_target = quick_action_target
|
2016-09-13 17:45:13 +05:30
|
|
|
@updates = {}
|
2019-10-12 21:52:04 +05:30
|
|
|
@execution_message = {}
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
content, commands = extractor.extract_commands(content, only: only)
|
2018-03-27 19:54:05 +05:30
|
|
|
extract_updates(commands)
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2022-07-16 23:28:13 +05:30
|
|
|
[content, @updates, execution_messages_for(commands), command_names(commands)]
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
# Takes a text and interprets the commands that are extracted from it.
|
|
|
|
# Returns the content without commands, and array of changes explained.
|
2019-07-07 11:18:12 +05:30
|
|
|
def explain(content, quick_action_target)
|
2017-09-10 17:25:29 +05:30
|
|
|
return [content, []] unless current_user.can?(:use_quick_actions)
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
@quick_action_target = quick_action_target
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
content, commands = extractor.extract_commands(content)
|
|
|
|
commands = explain_commands(commands)
|
2017-08-17 22:00:37 +05:30
|
|
|
[content, commands]
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
def failed_parse(message)
|
|
|
|
raise Gitlab::QuickActions::CommandDefinition::ParseError, message
|
|
|
|
end
|
|
|
|
|
2016-09-13 17:45:13 +05:30
|
|
|
def extractor
|
2017-09-10 17:25:29 +05:30
|
|
|
Gitlab::QuickActions::Extractor.new(self.class.command_definitions)
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
2022-08-13 15:12:31 +05:30
|
|
|
# Find users for commands like /assign
|
|
|
|
#
|
|
|
|
# eg. /assign me and @jane and jack
|
2017-08-17 22:00:37 +05:30
|
|
|
def extract_users(params)
|
2022-08-13 15:12:31 +05:30
|
|
|
Gitlab::QuickActions::UsersExtractor
|
|
|
|
.new(current_user, project: project, group: group, target: quick_action_target, text: params)
|
|
|
|
.execute
|
|
|
|
|
|
|
|
rescue Gitlab::QuickActions::UsersExtractor::Error => err
|
|
|
|
extract_users_failed(err)
|
|
|
|
end
|
|
|
|
|
|
|
|
def extract_users_failed(err)
|
|
|
|
case err
|
|
|
|
when Gitlab::QuickActions::UsersExtractor::MissingError
|
|
|
|
failed_parse(format(_("Failed to find users for %{missing}"), missing: err.message))
|
|
|
|
when Gitlab::QuickActions::UsersExtractor::TooManyRefsError
|
|
|
|
failed_parse(format(_('Too many references. Quick actions are limited to at most %{max_count} user references'),
|
|
|
|
max_count: err.limit))
|
|
|
|
when Gitlab::QuickActions::UsersExtractor::TooManyFoundError
|
|
|
|
failed_parse(format(_("Too many users found. Quick actions are limited to at most %{max_count} users"),
|
|
|
|
max_count: err.limit))
|
|
|
|
else
|
|
|
|
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(err)
|
|
|
|
failed_parse(_('Something went wrong'))
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
def find_milestones(project, params = {})
|
2020-03-13 15:44:24 +05:30
|
|
|
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
|
2018-03-27 19:54:05 +05:30
|
|
|
end
|
|
|
|
|
2019-02-15 15:39:39 +05:30
|
|
|
def parent
|
|
|
|
project || group
|
|
|
|
end
|
|
|
|
|
|
|
|
def group
|
|
|
|
strong_memoize(:group) do
|
2019-07-07 11:18:12 +05:30
|
|
|
quick_action_target.group if quick_action_target.respond_to?(:group)
|
2019-02-15 15:39:39 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_labels(labels_params = nil)
|
2019-07-07 11:18:12 +05:30
|
|
|
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)
|
|
|
|
|
2019-02-15 15:39:39 +05:30
|
|
|
finder_params = { include_ancestor_groups: true }
|
|
|
|
finder_params[:project_id] = project.id if project
|
|
|
|
finder_params[:group_id] = group.id if group
|
2019-07-07 11:18:12 +05:30
|
|
|
finder_params[:name] = extract_label_names(labels_params) if labels_params
|
2019-02-15 15:39:39 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
LabelsFinder.new(current_user, finder_params).execute
|
|
|
|
end
|
|
|
|
|
|
|
|
def label_with_tilde?(labels_params)
|
|
|
|
labels_params&.include?('~')
|
|
|
|
end
|
2019-02-15 15:39:39 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
def extract_label_names(labels_params)
|
|
|
|
# '"A" "A B C" A B' => ["A", "A B C", "A", "B"]
|
|
|
|
labels_params.scan(/"([^"]+)"|([^ ]+)/).flatten.compact
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2019-10-12 21:52:04 +05:30
|
|
|
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) }
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2016-09-13 17:45:13 +05:30
|
|
|
def find_label_ids(labels_param)
|
2017-08-17 22:00:37 +05:30
|
|
|
find_labels(labels_param).map(&:id)
|
|
|
|
end
|
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
def explain_commands(commands)
|
2019-10-12 21:52:04 +05:30
|
|
|
map_commands(commands, :explain)
|
|
|
|
end
|
|
|
|
|
|
|
|
def execution_messages_for(commands)
|
|
|
|
map_commands(commands, :execute_message).join(' ')
|
|
|
|
end
|
|
|
|
|
|
|
|
def map_commands(commands, method)
|
2023-03-04 22:38:38 +05:30
|
|
|
commands.map do |name_or_alias, arg|
|
|
|
|
definition = self.class.definition_by_name(name_or_alias)
|
2017-08-17 22:00:37 +05:30
|
|
|
next unless definition
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2019-10-12 21:52:04 +05:30
|
|
|
case method
|
|
|
|
when :explain
|
|
|
|
definition.explain(self, arg)
|
|
|
|
when :execute_message
|
2023-03-04 22:38:38 +05:30
|
|
|
@execution_message[definition.name.to_sym] || definition.execute_message(self, arg)
|
2019-10-12 21:52:04 +05:30
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
end.compact
|
2022-07-16 23:28:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def command_names(commands)
|
|
|
|
commands.flatten.map do |name|
|
|
|
|
definition = self.class.definition_by_name(name)
|
|
|
|
next unless definition
|
|
|
|
|
|
|
|
name
|
|
|
|
end.compact
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
def extract_updates(commands)
|
2017-08-17 22:00:37 +05:30
|
|
|
commands.each do |name, arg|
|
|
|
|
definition = self.class.definition_by_name(name)
|
|
|
|
next unless definition
|
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
definition.execute(self, arg)
|
2022-06-21 17:19:12 +05:30
|
|
|
usage_ping_tracking(definition.name, arg)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2016-09-13 17:45:13 +05:30
|
|
|
def extract_references(arg, type)
|
2019-02-15 15:39:39 +05:30
|
|
|
return [] unless arg
|
|
|
|
|
2016-09-13 17:45:13 +05:30
|
|
|
ext = Gitlab::ReferenceExtractor.new(project, current_user)
|
2018-05-09 12:01:36 +05:30
|
|
|
|
2019-02-15 15:39:39 +05:30
|
|
|
ext.analyze(arg, author: current_user, group: group)
|
2016-09-13 17:45:13 +05:30
|
|
|
|
|
|
|
ext.references(type)
|
|
|
|
end
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2021-03-11 19:13:27 +05:30
|
|
|
|
|
|
|
def usage_ping_tracking(quick_action_name, arg)
|
|
|
|
Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter.track_unique_action(
|
2022-06-21 17:19:12 +05:30
|
|
|
quick_action_name.to_s,
|
2021-03-11 19:13:27 +05:30
|
|
|
args: arg&.strip,
|
|
|
|
user: current_user
|
|
|
|
)
|
|
|
|
end
|
2022-04-04 11:22:00 +05:30
|
|
|
|
|
|
|
def can?(ability, object)
|
|
|
|
Ability.allowed?(current_user, ability, object)
|
|
|
|
end
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
end
|
2019-12-04 20:38:33 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
QuickActions::InterpretService.prepend_mod_with('QuickActions::InterpretService')
|