369 lines
14 KiB
Ruby
369 lines
14 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Gitlab
|
|
module QuickActions
|
|
module IssueActions
|
|
extend ActiveSupport::Concern
|
|
include Gitlab::QuickActions::Dsl
|
|
|
|
included do
|
|
# Issue only quick actions definition
|
|
desc { _('Set due date') }
|
|
explanation do |due_date|
|
|
_("Sets the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
|
|
end
|
|
execution_message do |due_date|
|
|
_("Set the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
|
|
end
|
|
params '<in 2 days | this Friday | December 31st>'
|
|
types Issue
|
|
condition do
|
|
quick_action_target.respond_to?(:due_date) &&
|
|
current_user.can?(:"set_#{quick_action_target.to_ability_name}_metadata", quick_action_target)
|
|
end
|
|
parse_params do |due_date_param|
|
|
Chronic.parse(due_date_param).try(:to_date)
|
|
end
|
|
command :due do |due_date|
|
|
if due_date
|
|
@updates[:due_date] = due_date
|
|
else
|
|
@execution_message[:due] = _('Failed to set due date because the date format is invalid.')
|
|
end
|
|
end
|
|
|
|
desc { _('Remove due date') }
|
|
explanation { _('Removes the due date.') }
|
|
execution_message { _('Removed the due date.') }
|
|
types Issue
|
|
condition do
|
|
quick_action_target.persisted? &&
|
|
quick_action_target.respond_to?(:due_date) &&
|
|
quick_action_target.due_date? &&
|
|
current_user.can?(:"set_#{quick_action_target.to_ability_name}_metadata", quick_action_target)
|
|
end
|
|
command :remove_due_date do
|
|
@updates[:due_date] = nil
|
|
end
|
|
|
|
desc { _('Move issue from one column of the board to another') }
|
|
explanation do |target_list_name|
|
|
label = find_label_references(target_list_name).first
|
|
_("Moves issue to %{label} column in the board.") % { label: label } if label
|
|
end
|
|
params '~"Target column"'
|
|
types Issue
|
|
condition do
|
|
current_user.can?(:"set_#{quick_action_target.to_ability_name}_metadata", quick_action_target) &&
|
|
quick_action_target.project.boards.count == 1
|
|
end
|
|
command :board_move do |target_list_name|
|
|
labels = find_labels(target_list_name)
|
|
label_ids = labels.map(&:id)
|
|
|
|
if label_ids.size > 1
|
|
message = _('Failed to move this issue because only a single label can be provided.')
|
|
elsif !Label.on_project_board?(quick_action_target.project_id, label_ids.first)
|
|
message = _('Failed to move this issue because label was not found.')
|
|
else
|
|
label_id = label_ids.first
|
|
|
|
@updates[:remove_label_ids] =
|
|
quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id) # rubocop: disable CodeReuse/ActiveRecord
|
|
@updates[:add_label_ids] = [label_id]
|
|
|
|
message = _("Moved issue to %{label} column in the board.") % { label: labels_to_reference(labels).first }
|
|
end
|
|
|
|
@execution_message[:board_move] = message
|
|
end
|
|
|
|
desc { _('Mark this issue as a duplicate of another issue') }
|
|
explanation do |duplicate_reference|
|
|
_("Marks this issue as a duplicate of %{duplicate_reference}.") % { duplicate_reference: duplicate_reference }
|
|
end
|
|
params '#issue'
|
|
types Issue
|
|
condition do
|
|
quick_action_target.persisted? &&
|
|
current_user.can?(:"set_#{quick_action_target.to_ability_name}_metadata", quick_action_target)
|
|
end
|
|
command :duplicate do |duplicate_param|
|
|
canonical_issue = extract_references(duplicate_param, :issue).first
|
|
|
|
if canonical_issue.present?
|
|
@updates[:canonical_issue_id] = canonical_issue.id
|
|
|
|
message = _("Marked this issue as a duplicate of %{duplicate_param}.") % { duplicate_param: duplicate_param }
|
|
else
|
|
message = _('Failed to mark this issue as a duplicate because referenced issue was not found.')
|
|
end
|
|
|
|
@execution_message[:duplicate] = message
|
|
end
|
|
|
|
desc { _('Clone this issue') }
|
|
explanation do |project = quick_action_target.project.full_path|
|
|
_("Clones this issue, without comments, to %{project}.") % { project: project }
|
|
end
|
|
params 'path/to/project [--with_notes]'
|
|
types Issue
|
|
condition do
|
|
quick_action_target.persisted? &&
|
|
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
|
|
end
|
|
command :clone do |params = ''|
|
|
params = params.split(' ')
|
|
with_notes = params.delete('--with_notes').present?
|
|
|
|
# If we have more than 1 param, then the user supplied too many spaces, or mistyped `--with_notes`
|
|
if params.size > 1
|
|
@execution_message[:clone] = _('Failed to clone this issue: wrong parameters.')
|
|
next
|
|
end
|
|
|
|
target_project_path = params[0]
|
|
target_project = target_project_path.present? ? Project.find_by_full_path(target_project_path) : quick_action_target.project
|
|
|
|
if target_project.present?
|
|
@updates[:target_clone_project] = target_project
|
|
@updates[:clone_with_notes] = with_notes
|
|
|
|
message = _("Cloned this issue to %{path_to_project}.") % { path_to_project: target_project_path || quick_action_target.project.full_path }
|
|
else
|
|
message = _("Failed to clone this issue because target project doesn't exist.")
|
|
end
|
|
|
|
@execution_message[:clone] = message
|
|
end
|
|
|
|
desc { _('Move this issue to another project.') }
|
|
explanation do |path_to_project|
|
|
_("Moves this issue to %{path_to_project}.") % { path_to_project: path_to_project }
|
|
end
|
|
params 'path/to/project'
|
|
types Issue
|
|
condition do
|
|
quick_action_target.persisted? &&
|
|
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
|
|
end
|
|
command :move do |target_project_path|
|
|
target_project = Project.find_by_full_path(target_project_path)
|
|
|
|
if target_project.present?
|
|
@updates[:target_project] = target_project
|
|
|
|
message = _("Moved this issue to %{path_to_project}.") % { path_to_project: target_project_path }
|
|
else
|
|
message = _("Failed to move this issue because target project doesn't exist.")
|
|
end
|
|
|
|
@execution_message[:move] = message
|
|
end
|
|
|
|
desc { _('Make issue confidential') }
|
|
explanation do
|
|
_('Makes this issue confidential.')
|
|
end
|
|
execution_message do
|
|
_('Made this issue confidential.')
|
|
end
|
|
types Issue
|
|
condition do
|
|
quick_action_target.issue_type_supports?(:confidentiality) &&
|
|
!quick_action_target.confidential? &&
|
|
current_user.can?(:set_confidentiality, quick_action_target)
|
|
end
|
|
command :confidential do
|
|
@updates[:confidential] = true
|
|
end
|
|
|
|
desc { _('Create a merge request') }
|
|
explanation do |branch_name = nil|
|
|
if branch_name
|
|
_("Creates branch '%{branch_name}' and a merge request to resolve this issue.") % { branch_name: branch_name }
|
|
else
|
|
_('Creates a branch and a merge request to resolve this issue.')
|
|
end
|
|
end
|
|
execution_message do |branch_name = nil|
|
|
if branch_name
|
|
_("Created branch '%{branch_name}' and a merge request to resolve this issue.") % { branch_name: branch_name }
|
|
else
|
|
_('Created a branch and a merge request to resolve this issue.')
|
|
end
|
|
end
|
|
params "<branch name>"
|
|
types Issue
|
|
condition do
|
|
current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project)
|
|
end
|
|
command :create_merge_request do |branch_name = nil|
|
|
@updates[:create_merge_request] = {
|
|
branch_name: branch_name,
|
|
issue_iid: quick_action_target.iid
|
|
}
|
|
end
|
|
|
|
desc { _('Add Zoom meeting') }
|
|
explanation { _('Adds a Zoom meeting.') }
|
|
params do
|
|
zoom_link_params
|
|
end
|
|
types Issue
|
|
condition do
|
|
@zoom_service = zoom_link_service
|
|
|
|
@zoom_service.can_add_link?
|
|
end
|
|
parse_params do |link_params|
|
|
@zoom_service.parse_link(link_params)
|
|
end
|
|
command :zoom do |link, link_text = nil|
|
|
result = add_zoom_link(link, link_text)
|
|
@execution_message[:zoom] = result.message
|
|
merge_updates(result, @updates)
|
|
end
|
|
|
|
desc { _('Remove Zoom meeting') }
|
|
explanation { _('Remove Zoom meeting.') }
|
|
execution_message { _('Zoom meeting removed') }
|
|
types Issue
|
|
condition do
|
|
@zoom_service = zoom_link_service
|
|
@zoom_service.can_remove_link?
|
|
end
|
|
command :remove_zoom do
|
|
result = @zoom_service.remove_link
|
|
@execution_message[:remove_zoom] = result.message
|
|
end
|
|
|
|
desc { _('Add email participant(s)') }
|
|
explanation { _('Adds email participant(s).') }
|
|
params 'email1@example.com email2@example.com (up to 6 emails)'
|
|
types Issue
|
|
condition do
|
|
Feature.enabled?(:issue_email_participants, parent) &&
|
|
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
|
|
end
|
|
command :invite_email do |emails = ""|
|
|
MAX_NUMBER_OF_EMAILS = 6
|
|
|
|
existing_emails = quick_action_target.email_participants_emails_downcase
|
|
emails_to_add = emails.split(' ').index_by { |email| [email.downcase, email] }.except(*existing_emails).each_value.first(MAX_NUMBER_OF_EMAILS)
|
|
added_emails = []
|
|
|
|
emails_to_add.each do |email|
|
|
new_participant = quick_action_target.issue_email_participants.create(email: email)
|
|
added_emails << email if new_participant.persisted?
|
|
end
|
|
|
|
if added_emails.any?
|
|
message = _("added %{emails}") % { emails: added_emails.to_sentence }
|
|
SystemNoteService.add_email_participants(quick_action_target, quick_action_target.project, current_user, message)
|
|
@execution_message[:invite_email] = message.upcase_first << "."
|
|
else
|
|
@execution_message[:invite_email] = _("No email participants were added. Either none were provided, or they already exist.")
|
|
end
|
|
end
|
|
|
|
desc { _('Promote issue to incident') }
|
|
explanation { _('Promotes issue to incident') }
|
|
types Issue
|
|
condition do
|
|
quick_action_target.persisted? &&
|
|
!quick_action_target.incident? &&
|
|
current_user.can?(:update_issue, quick_action_target)
|
|
end
|
|
command :promote_to_incident do
|
|
issue = ::Issues::UpdateService
|
|
.new(project: quick_action_target.project, current_user: current_user, params: { issue_type: 'incident' })
|
|
.execute(quick_action_target)
|
|
|
|
@execution_message[:promote_to_incident] =
|
|
if issue.incident?
|
|
_('Issue has been promoted to incident')
|
|
else
|
|
_('Failed to promote issue to incident')
|
|
end
|
|
end
|
|
|
|
desc { _('Add customer relation contacts') }
|
|
explanation { _('Add customer relation contact(s).') }
|
|
params '[contact:contact@example.com] [contact:person@example.org]'
|
|
types Issue
|
|
condition do
|
|
current_user.can?(:set_issue_crm_contacts, quick_action_target) &&
|
|
CustomerRelations::Contact.exists_for_group?(quick_action_target.project.root_ancestor)
|
|
end
|
|
execution_message do
|
|
_('One or more contacts were successfully added.')
|
|
end
|
|
command :add_contacts do |contact_emails|
|
|
@updates[:add_contacts] = contact_emails.split(' ')
|
|
end
|
|
|
|
desc { _('Remove customer relation contacts') }
|
|
explanation { _('Remove customer relation contact(s).') }
|
|
params '[contact:contact@example.com] [contact:person@example.org]'
|
|
types Issue
|
|
condition do
|
|
current_user.can?(:set_issue_crm_contacts, quick_action_target) &&
|
|
quick_action_target.customer_relations_contacts.exists?
|
|
end
|
|
execution_message do
|
|
_('One or more contacts were successfully removed.')
|
|
end
|
|
command :remove_contacts do |contact_emails|
|
|
@updates[:remove_contacts] = contact_emails.split(' ')
|
|
end
|
|
|
|
desc { _('Add a timeline event to incident') }
|
|
explanation { _('Adds a timeline event to incident.') }
|
|
params '<timeline comment> | <date(YYYY-MM-DD)> <time(HH:MM)>'
|
|
types Issue
|
|
condition do
|
|
quick_action_target.incident? &&
|
|
current_user.can?(:admin_incident_management_timeline_event, quick_action_target)
|
|
end
|
|
parse_params do |event_params|
|
|
Gitlab::QuickActions::TimelineTextAndDateTimeSeparator.new(event_params).execute
|
|
end
|
|
command :timeline do |event_text, date_time|
|
|
if event_text && date_time
|
|
timeline_event = timeline_event_create_service(event_text, date_time).execute
|
|
|
|
@execution_message[:timeline] =
|
|
if timeline_event.success?
|
|
_('Timeline event added successfully.')
|
|
else
|
|
_('Something went wrong while adding timeline event.')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def zoom_link_service
|
|
::Issues::ZoomLinkService.new(project: quick_action_target.project, current_user: current_user, params: { issue: quick_action_target })
|
|
end
|
|
|
|
def zoom_link_params
|
|
'<Zoom URL>'
|
|
end
|
|
|
|
def add_zoom_link(link, _link_text)
|
|
zoom_link_service.add_link(link)
|
|
end
|
|
|
|
def merge_updates(result, update_hash)
|
|
update_hash.merge!(result.payload) if result.payload
|
|
end
|
|
|
|
def timeline_event_create_service(event_text, event_date_time)
|
|
::IncidentManagement::TimelineEvents::CreateService.new(quick_action_target, current_user, { note: event_text, occurred_at: event_date_time, editable: true })
|
|
end
|
|
end
|
|
end
|
|
end
|