Commit ea11a86f authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '56100-make-quick-action-commands-applied-banner-more-useful-ee' into 'master'

EE Port: Make quick action "commands applied" banner more useful

See merge request gitlab-org/gitlab-ee!14630
parents 1463e91e 32bb87e3
......@@ -137,6 +137,10 @@ class Label < ApplicationRecord
where(id: ids)
end
def self.on_project_board?(project_id, label_id)
on_project_boards(project_id).where(id: label_id).exists?
end
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
......
......@@ -21,7 +21,7 @@ module Notes
if quick_actions_service.supported?(note)
options = { merge_request_diff_head_sha: merge_request_diff_head_sha }
content, update_params = quick_actions_service.execute(note, options)
content, update_params, message = quick_actions_service.execute(note, options)
only_commands = content.empty?
......@@ -52,7 +52,7 @@ module Notes
# We must add the error after we call #save because errors are reset
# when #save is called
if only_commands
note.errors.add(:commands_only, 'Commands applied')
note.errors.add(:commands_only, message.presence || _('Commands did not apply'))
end
end
......
......@@ -31,17 +31,19 @@ module QuickActions
end
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
# 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)
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]
[content, @updates, execution_messages_for(commands)]
end
# Takes a text and interprets the commands that are extracted from it.
......@@ -119,8 +121,12 @@ module QuickActions
labels_params.scan(/"([^"]+)"|([^ ]+)/).flatten.compact
end
def find_label_references(labels_param)
find_labels(labels_param).map(&:to_reference)
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)
......@@ -128,11 +134,24 @@ module QuickActions
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
definition.explain(self, arg)
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
......
---
title: Make quick action commands applied banner more useful
merge_request: 26672
author: Jacopo Beschi @jacopo-beschi
type: added
......@@ -9,7 +9,8 @@ and commits that are usually done by clicking buttons or dropdowns in GitLab's U
You can enter these commands while creating a new issue or merge request, or
in comments of issues, epics, merge requests, and commits. Each command should be
on a separate line in order to be properly detected and executed. Once executed,
the commands are removed from the text body and not visible to anyone else.
> From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26672), an alert is displayed when a quick action is successfully applied.
## Quick Actions for issues and merge requests
......
......@@ -22,6 +22,7 @@ module EE
if child_epic && !quick_action_target.child?(child_epic.id)
EpicLinks::CreateService.new(quick_action_target, current_user, { target_issuable: child_epic }).execute
@execution_message[:child_epic] = _("Added %{epic_ref} as child epic.") % { epic_ref: child_epic.to_reference(quick_action_target) }
end
end
......@@ -29,7 +30,7 @@ module EE
explanation do |epic_param|
child_epic = extract_epic(epic_param)
_("Removes %{epic_ref} from child epics.") % { epic_ref: child_epic.to_reference(quick_action_target) } if child_epic
_("Removes %{epic_ref} from child epics.") % { epic_ref: child_epic.to_reference(quick_action_target) }
end
types Epic
condition { action_allowed? }
......@@ -39,6 +40,7 @@ module EE
if child_epic && quick_action_target.child?(child_epic.id)
EpicLinks::DestroyService.new(child_epic, current_user).execute
@execution_message[:remove_child_epic] = _("Removed %{epic_ref} from child epics.") % { epic_ref: child_epic.to_reference(quick_action_target) }
end
end
......@@ -56,6 +58,7 @@ module EE
if parent_epic && !parent_epic.child?(quick_action_target.id)
EpicLinks::CreateService.new(parent_epic, current_user, { target_issuable: quick_action_target }).execute
@execution_message[:parent_epic] = _("Set %{epic_ref} as parent epic.") % { epic_ref: parent_epic.to_reference(quick_action_target) }
end
end
......@@ -70,7 +73,9 @@ module EE
action_allowed? && quick_action_target.parent.present?
end
command :remove_parent_epic do
parent_epic = quick_action_target.parent
EpicLinks::DestroyService.new(quick_action_target, current_user).execute
@execution_message[:remove_parent_epic] = _('Removed parent epic %{epic_ref}.') % { epic_ref: parent_epic.to_reference(quick_action_target) }
end
private
......
......@@ -8,8 +8,9 @@ module EE
include ::Gitlab::QuickActions::Dsl
included do
desc 'Add to epic'
explanation 'Adds an issue to an epic.'
desc _('Add to epic')
explanation _('Adds an issue to an epic.')
execution_message _('Added an issue to an epic.')
types Issue
condition do
quick_action_target.project.group&.feature_available?(:epics) &&
......@@ -20,8 +21,9 @@ module EE
@updates[:epic] = extract_epic(epic_param)
end
desc 'Remove from epic'
explanation 'Removes an issue from an epic.'
desc _('Remove from epic')
explanation _('Removes an issue from an epic.')
execution_message _('Removed an issue from an epic.')
types Issue
condition do
quick_action_target.persisted? &&
......@@ -32,9 +34,9 @@ module EE
@updates[:epic] = nil
end
desc 'Promote issue to an epic'
explanation 'Promote issue to an epic.'
warning 'may expose confidential information'
desc _('Promote issue to an epic')
explanation _('Promote issue to an epic.')
warning _('may expose confidential information')
types Issue
condition do
quick_action_target.persisted? &&
......@@ -43,6 +45,7 @@ module EE
end
command :promote do
Epics::IssuePromoteService.new(quick_action_target.project, current_user).execute(quick_action_target)
@execution_message[:promote] = _('Promoted issue to an epic.')
end
def extract_epic(params)
......
......@@ -9,9 +9,8 @@ module EE
included do
desc _('Change assignee(s)')
explanation do
_('Change assignee(s).')
end
explanation _('Change assignee(s).')
execution_message _('Changed assignee(s).')
params '@user1 @user2'
types Issue, MergeRequest
condition do
......@@ -27,6 +26,7 @@ module EE
explanation do |weight|
_("Sets weight to %{weight}.") % { weight: weight } if weight
end
params "0, 1, 2, …"
types Issue, MergeRequest
condition do
......@@ -37,11 +37,15 @@ module EE
weight.to_i if weight.to_i >= 0
end
command :weight do |weight|
@updates[:weight] = weight if weight
if weight
@updates[:weight] = weight
@execution_message[:weight] = _("Set weight to %{weight}.") % { weight: weight }
end
end
desc _('Clear weight')
explanation _('Clears weight.')
execution_message _('Cleared weight.')
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
......
......@@ -8,8 +8,8 @@ module EE
include ::Gitlab::QuickActions::Dsl
included do
desc 'Approve a merge request'
explanation 'Approve the current merge request.'
desc _('Approve a merge request')
explanation _('Approve the current merge request.')
types MergeRequest
condition do
quick_action_target.persisted? && quick_action_target.can_approve?(current_user) && !quick_action_target.project.require_password_to_approve?
......@@ -17,6 +17,7 @@ module EE
command :approve do
if quick_action_target.can_approve?(current_user)
::MergeRequests::ApprovalService.new(quick_action_target.project, current_user).execute(quick_action_target)
@execution_message[:approve] = _('Approved the current merge request.')
end
end
end
......
......@@ -12,6 +12,9 @@ module EE
explanation do |related_reference|
_('Marks this issue as related to %{issue_ref}.') % { issue_ref: related_reference }
end
execution_message do |related_reference|
_('Marked this issue as related to %{issue_ref}.') % { issue_ref: related_reference }
end
params '#issue'
types Issue
condition do
......
......@@ -18,7 +18,7 @@ describe 'Issue promotion', :js do
it 'does not promote the issue' do
visit project_issue_path(project, issue)
expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content 'Promoted issue to an epic.'
expect(issue.reload).to be_open
expect(Epic.count).to be_zero
......@@ -36,7 +36,7 @@ describe 'Issue promotion', :js do
end
it 'does not promote the issue' do
expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content 'Promoted issue to an epic.'
expect(issue.reload).to be_open
expect(Epic.count).to be_zero
......@@ -56,7 +56,7 @@ describe 'Issue promotion', :js do
epic = Epic.last
expect(page).to have_content 'Commands applied'
expect(page).to have_content 'Promoted issue to an epic.'
expect(issue.reload).to be_closed
expect(epic.title).to eq(issue.title)
expect(epic.description).to eq(issue.description)
......
......@@ -3,8 +3,8 @@
module Gitlab
module QuickActions
class CommandDefinition
attr_accessor :name, :aliases, :description, :explanation, :params,
:condition_block, :parse_params_block, :action_block, :warning, :types
attr_accessor :name, :aliases, :description, :explanation, :execution_message,
:params, :condition_block, :parse_params_block, :action_block, :warning, :types
def initialize(name, attributes = {})
@name = name
......@@ -13,6 +13,7 @@ module Gitlab
@description = attributes[:description] || ''
@warning = attributes[:warning] || ''
@explanation = attributes[:explanation] || ''
@execution_message = attributes[:execution_message] || ''
@params = attributes[:params] || []
@condition_block = attributes[:condition_block]
@parse_params_block = attributes[:parse_params_block]
......@@ -48,13 +49,23 @@ module Gitlab
end
def execute(context, arg)
return if noop? || !available?(context)
return unless executable?(context)
count_commands_executed_in(context)
execute_block(action_block, context, arg)
end
def execute_message(context, arg)
return unless executable?(context)
if execution_message.respond_to?(:call)
execute_block(execution_message, context, arg)
else
execution_message
end
end
def to_h(context)
desc = description
if desc.respond_to?(:call)
......@@ -77,6 +88,10 @@ module Gitlab
private
def executable?(context)
!noop? && available?(context)
end
def count_commands_executed_in(context)
return unless context.respond_to?(:commands_executed_count=)
......
......@@ -16,6 +16,13 @@ module Gitlab
_("Tags this commit to %{tag_name}.") % { tag_name: tag_name }
end
end
execution_message do |tag_name, message|
if message.present?
_("Tagged this commit to %{tag_name} with \"%{message}\".") % { tag_name: tag_name, message: message }
else
_("Tagged this commit to %{tag_name}.") % { tag_name: tag_name }
end
end
params 'v1.2.3 <message>'
parse_params do |tag_name_and_message|
tag_name_and_message.split(' ', 2)
......
......@@ -66,6 +66,35 @@ module Gitlab
@explanation = block_given? ? block : text
end
# Allows to provide a message about quick action execution result, success or failure.
# This message is shown after quick action execution and after saving the note.
#
# Example:
#
# execution_message do |arguments|
# "Added label(s) #{arguments.join(' ')}"
# end
# command :command_key do |arguments|
# # Awesome code block
# end
#
# Note: The execution_message won't be executed unless the condition block returns true.
# execution_message block is executed always after the command block has run,
# for this reason if the condition block doesn't return true after the command block has
# run you need to set the @execution_message variable inside the command block instead as
# shown in the following example.
#
# Example using instance variable:
#
# command :command_key do |arguments|
# # Awesome code block
# @execution_message[:command_key] = 'command_key executed successfully'
# end
#
def execution_message(text = '', &block)
@execution_message = block_given? ? block : text
end
# Allows to define type(s) that must be met in order for the command
# to be returned by `.command_names` & `.command_definitions`.
#
......@@ -121,10 +150,16 @@ module Gitlab
# comment.
# It accepts aliases and takes a block.
#
# You can also set the @execution_message instance variable, on conflicts with
# execution_message method the instance variable has precedence.
#
# Example:
#
# command :my_command, :alias_for_my_command do |arguments|
# # Awesome code block
# @updates[:my_command] = 'foo'
#
# @execution_message[:my_command] = 'my_command executed successfully'
# end
def command(*command_names, &block)
define_command(CommandDefinition, *command_names, &block)
......@@ -158,6 +193,7 @@ module Gitlab
description: @description,
warning: @warning,
explanation: @explanation,
execution_message: @execution_message,
params: @params,
condition_block: @condition_block,
parse_params_block: @parse_params_block,
......@@ -173,6 +209,7 @@ module Gitlab
@description = nil
@explanation = nil
@execution_message = nil
@params = nil
@condition_block = nil
@warning = nil
......
......@@ -12,10 +12,16 @@ module Gitlab
included do
# Issue, MergeRequest, Epic: quick actions definitions
desc do
"Close this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
_('Close this %{quick_action_target}') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
explanation do
"Closes this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
_('Closes this %{quick_action_target}.') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
execution_message do
_('Closed this %{quick_action_target}.') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
......@@ -28,10 +34,16 @@ module Gitlab
end
desc do
"Reopen this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
_('Reopen this %{quick_action_target}') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
explanation do
"Reopens this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
_('Reopens this %{quick_action_target}.') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
execution_message do
_('Reopened this %{quick_action_target}.') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
......@@ -45,7 +57,10 @@ module Gitlab
desc _('Change title')
explanation do |title_param|
_("Changes the title to \"%{title_param}\".") % { title_param: title_param }
_('Changes the title to "%{title_param}".') % { title_param: title_param }
end
execution_message do |title_param|
_('Changed the title to "%{title_param}".') % { title_param: title_param }
end
params '<New title>'
types Issuable
......@@ -61,7 +76,10 @@ module Gitlab
explanation do |labels_param|
labels = find_label_references(labels_param)
"Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
if labels.any?
_("Adds %{labels} %{label_text}.") %
{ labels: labels.join(' '), label_text: 'label'.pluralize(labels.count) }
end
end
params '~label1 ~"label 2"'
types Issuable
......@@ -71,21 +89,15 @@ module Gitlab
find_labels.any?
end
command :label do |labels_param|
label_ids = find_label_ids(labels_param)
if label_ids.any?
@updates[:add_label_ids] ||= []
@updates[:add_label_ids] += label_ids
@updates[:add_label_ids].uniq!
end
run_label_command(labels: find_labels(labels_param), command: :label, updates_key: :add_label_ids)
end
desc _('Remove all or specific label(s)')
explanation do |labels_param = nil|
if labels_param.present?
labels = find_label_references(labels_param)
"Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
label_references = labels_param.present? ? find_label_references(labels_param) : []
if label_references.any?
_("Removes %{label_references} %{label_text}.") %
{ label_references: label_references.join(' '), label_text: 'label'.pluralize(label_references.count) }
else
_('Removes all labels.')
end
......@@ -99,7 +111,9 @@ module Gitlab
end
command :unlabel do |labels_param = nil|
if labels_param.present?
label_ids = find_label_ids(labels_param)
labels = find_labels(labels_param)
label_ids = labels.map(&:id)
label_references = labels_to_reference(labels, :name)
if label_ids.any?
@updates[:remove_label_ids] ||= []
......@@ -109,7 +123,10 @@ module Gitlab
end
else
@updates[:label_ids] = []
label_references = []
end
@execution_message[:unlabel] = remove_label_message(label_references)
end
desc _('Replace all label(s)')
......@@ -125,18 +142,12 @@ module Gitlab
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent)
end
command :relabel do |labels_param|
label_ids = find_label_ids(labels_param)
if label_ids.any?
@updates[:label_ids] ||= []
@updates[:label_ids] += label_ids
@updates[:label_ids].uniq!
end
run_label_command(labels: find_labels(labels_param), command: :relabel, updates_key: :label_ids)
end
desc _('Add a todo')
explanation _('Adds a todo.')
execution_message _('Added a todo.')
types Issuable
condition do
quick_action_target.persisted? &&
......@@ -148,6 +159,7 @@ module Gitlab
desc _('Mark to do as done')
explanation _('Marks to do as done.')
execution_message _('Marked to do as done.')
types Issuable
condition do
quick_action_target.persisted? &&
......@@ -159,7 +171,12 @@ module Gitlab
desc _('Subscribe')
explanation do
"Subscribes to this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
_('Subscribes to this %{quick_action_target}.') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
execution_message do
_('Subscribed to this %{quick_action_target}.') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
......@@ -172,7 +189,12 @@ module Gitlab
desc _('Unsubscribe')
explanation do
"Unsubscribes from this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
_('Unsubscribes from this %{quick_action_target}.') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
execution_message do
_('Unsubscribed from this %{quick_action_target}.') %
{ quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
......@@ -187,6 +209,9 @@ module Gitlab
explanation do |name|
_("Toggles :%{name}: emoji award.") % { name: name } if name
end
execution_message do |name|
_("Toggled :%{name}: emoji award.") % { name: name } if name
end
params ':emoji:'
types Issuable
condition do
......@@ -215,6 +240,41 @@ module Gitlab
substitution :tableflip do |comment|
"#{comment} #{TABLEFLIP}"
end
private
def run_label_command(labels:, command:, updates_key:)
return if labels.empty?
@updates[updates_key] ||= []
@updates[updates_key] += labels.map(&:id)
@updates[updates_key].uniq!
label_references = labels_to_reference(labels, :name)
@execution_message[command] = case command
when :relabel
_('Replaced all labels with %{label_references} %{label_text}.') %
{
label_references: label_references.join(' '),
label_text: 'label'.pluralize(label_references.count)
}
when :label
_('Added %{label_references} %{label_text}.') %
{
label_references: label_references.join(' '),
label_text: 'label'.pluralize(labels.count)
}
end
end
def remove_label_message(label_references)
if label_references.any?
_("Removed %{label_references} %{label_text}.") %
{ label_references: label_references.join(' '), label_text: 'label'.pluralize(label_references.count) }
else
_('Removed all labels.')
end
end
end
end
end
......
......@@ -12,6 +12,9 @@ module Gitlab
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
......@@ -27,6 +30,7 @@ module Gitlab
desc _('Remove due date')
explanation _('Removes the due date.')
execution_message _('Removed the due date.')
types Issue
condition do
quick_action_target.persisted? &&
......@@ -49,22 +53,27 @@ module Gitlab
current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) &&
quick_action_target.project.boards.count == 1
end
# rubocop: disable CodeReuse/ActiveRecord
command :board_move do |target_list_name|
label_ids = find_label_ids(target_list_name)
labels = find_labels(target_list_name)
label_ids = labels.map(&:id)
if label_ids.size == 1
label_id = label_ids.first
# Ensure this label corresponds to a list on the board
next unless Label.on_project_boards(quick_action_target.project_id).where(id: label_id).exists?
next unless Label.on_project_board?(quick_action_target.project_id, label_id)
@updates[:remove_label_ids] =
quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id)
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 }
else
message = _('Move this issue failed because you need to specify only one label.')
end
@execution_message[:board_move] = message
end
# rubocop: enable CodeReuse/ActiveRecord
desc _('Mark this issue as a duplicate of another issue')
explanation do |duplicate_reference|
......@@ -81,7 +90,13 @@ module Gitlab
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 = _('Mark as duplicate failed because referenced issue was not found')
end
@execution_message[:duplicate] = message
end
desc _('Move this issue to another project.')
......@@ -99,13 +114,22 @@ module Gitlab
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 = _("Move this issue failed because target project doesn't exists")
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
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
......@@ -119,7 +143,14 @@ module Gitlab
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"
_('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>"
......
......@@ -9,12 +9,9 @@ module Gitlab
included do
# Issue, MergeRequest: quick actions definitions
desc _('Assign')
# rubocop: disable CodeReuse/ActiveRecord
explanation do |users|
users = quick_action_target.allows_multiple_assignees? ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}."
_('Assigns %{assignee_users_sentence}.') % { assignee_users_sentence: assignee_users_sentence(users) }
end
# rubocop: enable CodeReuse/ActiveRecord
params do
quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
......@@ -26,7 +23,10 @@ module Gitlab
extract_users(assignee_param)
end
command :assign do |users|
next if users.empty?
if users.empty?
@execution_message[:assign] = _("Assign command failed because no user was found")
next
end
if quick_action_target.allows_multiple_assignees?
@updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
......@@ -34,6 +34,8 @@ module Gitlab
else
@updates[:assignee_ids] = [users.first.id]
end
@execution_message[:assign] = _('Assigned %{assignee_users_sentence}.') % { assignee_users_sentence: assignee_users_sentence(users) }
end
desc do
......@@ -44,9 +46,14 @@ module Gitlab
end
end
explanation do |users = nil|
assignees = quick_action_target.assignees
assignees &= users if users.present? && quick_action_target.allows_multiple_assignees?
"Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
assignees = assignees_for_removal(users)
_("Removes %{assignee_text} %{assignee_references}.") %
{ assignee_text: 'assignee'.pluralize(assignees.size), assignee_references: assignees.map(&:to_reference).to_sentence }
end
execution_message do |users = nil|
assignees = assignees_for_removal(users)
_("Removed %{assignee_text} %{assignee_references}.") %
{ assignee_text: 'assignee'.pluralize(assignees.size), assignee_references: assignees.map(&:to_reference).to_sentence }
end
params do
quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : ''
......@@ -74,6 +81,9 @@ module Gitlab
explanation do |milestone|
_("Sets the milestone to %{milestone_reference}.") % { milestone_reference: milestone.to_reference } if milestone
end
execution_message do |milestone|
_("Set the milestone to %{milestone_reference}.") % { milestone_reference: milestone.to_reference } if milestone
end
params '%"milestone"'
types Issue, MergeRequest
condition do
......@@ -92,6 +102,9 @@ module Gitlab
explanation do
_("Removes %{milestone_reference} milestone.") % { milestone_reference: quick_action_target.milestone.to_reference(format: :name) }
end
execution_message do
_("Removed %{milestone_reference} milestone.") % { milestone_reference: quick_action_target.milestone.to_reference(format: :name) }
end
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
......@@ -116,17 +129,22 @@ module Gitlab
extract_references(issuable_param, :merge_request).first
end
command :copy_metadata do |source_issuable|
if source_issuable.present? && source_issuable.project.id == quick_action_target.project.id
if can_copy_metadata?(source_issuable)
@updates[:add_label_ids] = source_issuable.labels.map(&:id)
@updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
@execution_message[:copy_metadata] = _("Copied labels and milestone from %{source_issuable_reference}.") % { source_issuable_reference: source_issuable.to_reference }
end
end
desc _('Set time estimate')
explanation do |time_estimate|
time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
_("Sets time estimate to %{time_estimate}.") % { time_estimate: time_estimate } if time_estimate
formatted_time_estimate = format_time_estimate(time_estimate)
_("Sets time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
end
execution_message do |time_estimate|
formatted_time_estimate = format_time_estimate(time_estimate)
_("Set time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
end
params '<1w 3d 2h 14m>'
types Issue, MergeRequest
......@@ -144,18 +162,12 @@ module Gitlab
desc _('Add or subtract spent time')
explanation do |time_spent, time_spent_date|
if time_spent
if time_spent > 0
verb = _('Adds')
value = time_spent
else
verb = _('Subtracts')
value = -time_spent
end
_("%{verb} %{time_spent_value} spent time.") % { verb: verb, time_spent_value: Gitlab::TimeTrackingFormatter.output(value) }
end
spend_time_message(time_spent, time_spent_date, false)
end
execution_message do |time_spent, time_spent_date|
spend_time_message(time_spent, time_spent_date, true)
end
params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
types Issue, MergeRequest
condition do
......@@ -176,6 +188,7 @@ module Gitlab
desc _('Remove time estimate')
explanation _('Removes time estimate.')
execution_message _('Removed time estimate.')
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
......@@ -187,6 +200,7 @@ module Gitlab
desc _('Remove spent time')
explanation _('Removes spent time.')
execution_message _('Removed spent time.')
condition do
quick_action_target.persisted? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
......@@ -198,6 +212,7 @@ module Gitlab
desc _("Lock the discussion")
explanation _("Locks the discussion")
execution_message _("Locked the discussion")
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
......@@ -210,6 +225,7 @@ module Gitlab
desc _("Unlock the discussion")
explanation _("Unlocks the discussion")
execution_message _("Unlocked the discussion")
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
......@@ -219,6 +235,47 @@ module Gitlab
command :unlock do
@updates[:discussion_locked] = false
end
private
def assignee_users_sentence(users)
if quick_action_target.allows_multiple_assignees?
users
else
[users.first]
end.map(&:to_reference).to_sentence
end
def assignees_for_removal(users)
assignees = quick_action_target.assignees
if users.present? && quick_action_target.allows_multiple_assignees?
assignees & users
else
assignees
end
end
def can_copy_metadata?(source_issuable)
source_issuable.present? && source_issuable.project_id == quick_action_target.project_id
end
def format_time_estimate(time_estimate)
Gitlab::TimeTrackingFormatter.output(time_estimate)
end
def spend_time_message(time_spent, time_spent_date, paste_tense)
return unless time_spent
if time_spent > 0
verb = paste_tense ? _('Added') : _('Adds')
value = time_spent
else
verb = paste_tense ? _('Subtracted') : _('Subtracts')
value = -time_spent
end
_("%{verb} %{time_spent_value} spent time.") % { verb: verb, time_spent_value: format_time_estimate(value) }
end
end
end
end
......
......@@ -8,8 +8,9 @@ module Gitlab
included do
# MergeRequest only quick actions definitions
desc 'Merge (when the pipeline succeeds)'
explanation 'Merges this merge request when the pipeline succeeds.'
desc _('Merge (when the pipeline succeeds)')
explanation _('Merges this merge request when the pipeline succeeds.')
execution_message _('Scheduled to merge this merge request when the pipeline succeeds.')
types MergeRequest
condition do
last_diff_sha = params && params[:merge_request_diff_head_sha]
......@@ -22,10 +23,22 @@ module Gitlab
desc 'Toggle the Work In Progress status'
explanation do
verb = quick_action_target.work_in_progress? ? 'Unmarks' : 'Marks'
noun = quick_action_target.to_ability_name.humanize(capitalize: false)
"#{verb} this #{noun} as Work In Progress."
if quick_action_target.work_in_progress?
_("Unmarks this %{noun} as Work In Progress.")
else
_("Marks this %{noun} as Work In Progress.")
end % { noun: noun }
end
execution_message do
noun = quick_action_target.to_ability_name.humanize(capitalize: false)
if quick_action_target.work_in_progress?
_("Unmarked this %{noun} as Work In Progress.")
else
_("Marked this %{noun} as Work In Progress.")
end % { noun: noun }
end
types MergeRequest
condition do
quick_action_target.respond_to?(:work_in_progress?) &&
......@@ -36,9 +49,12 @@ module Gitlab
@updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip'
end
desc 'Set target branch'
desc _('Set target branch')
explanation do |branch_name|
"Sets target branch to #{branch_name}."
_('Sets target branch to %{branch_name}.') % { branch_name: branch_name }
end
execution_message do |branch_name|
_('Set target branch to %{branch_name}.') % { branch_name: branch_name }
end
params '<Local branch name>'
types MergeRequest
......
This diff is collapsed.
......@@ -219,6 +219,52 @@ describe Gitlab::QuickActions::CommandDefinition do
end
end
describe "#execute_message" do
context "when the command is a noop" do
it 'returns nil' do
expect(subject.execute_message({}, nil)).to be_nil
end
end
context "when the command is not a noop" do
before do
subject.action_block = proc { self.run = true }
end
context "when the command is not available" do
before do
subject.condition_block = proc { false }
end
it 'returns nil' do
expect(subject.execute_message({}, nil)).to be_nil
end
end
context "when the command is available" do
context 'when the execution_message is a static string' do
before do
subject.execution_message = 'Assigned jacopo'
end
it 'returns this static string' do
expect(subject.execute_message({}, nil)).to eq('Assigned jacopo')
end
end
context 'when the explanation is dynamic' do
before do
subject.execution_message = proc { |arg| "Assigned #{arg}" }
end
it 'invokes the proc' do
expect(subject.execute_message({}, 'Jacopo')).to eq('Assigned Jacopo')
end
end
end
end
end
describe '#explain' do
context 'when the command is not available' do
before do
......
......@@ -20,6 +20,9 @@ describe Gitlab::QuickActions::Dsl do
desc do
"A dynamic description for #{noteable.upcase}"
end
execution_message do |arg|
"A dynamic execution message for #{noteable.upcase} passing #{arg}"
end
params 'The first argument', 'The second argument'
command :dynamic_description do |args|
args.split
......@@ -30,6 +33,7 @@ describe Gitlab::QuickActions::Dsl do
explanation do |arg|
"Action does something with #{arg}"
end
execution_message 'Command applied correctly'
condition do
project == 'foo'
end
......@@ -67,6 +71,7 @@ describe Gitlab::QuickActions::Dsl do
expect(no_args_def.aliases).to eq([:none])
expect(no_args_def.description).to eq('A command with no args')
expect(no_args_def.explanation).to eq('')
expect(no_args_def.execution_message).to eq('')
expect(no_args_def.params).to eq([])
expect(no_args_def.condition_block).to be_nil
expect(no_args_def.types).to eq([])
......@@ -78,6 +83,8 @@ describe Gitlab::QuickActions::Dsl do
expect(explanation_with_aliases_def.aliases).to eq([:once, :first])
expect(explanation_with_aliases_def.description).to eq('')
expect(explanation_with_aliases_def.explanation).to eq('Static explanation')
expect(explanation_with_aliases_def.execution_message).to eq('')
expect(no_args_def.params).to eq([])
expect(explanation_with_aliases_def.params).to eq(['The first argument'])
expect(explanation_with_aliases_def.condition_block).to be_nil
expect(explanation_with_aliases_def.types).to eq([])
......@@ -88,7 +95,7 @@ describe Gitlab::QuickActions::Dsl do
expect(dynamic_description_def.name).to eq(:dynamic_description)
expect(dynamic_description_def.aliases).to eq([])
expect(dynamic_description_def.to_h(OpenStruct.new(noteable: 'issue'))[:description]).to eq('A dynamic description for ISSUE')
expect(dynamic_description_def.explanation).to eq('')
expect(dynamic_description_def.execute_message(OpenStruct.new(noteable: 'issue'), 'arg')).to eq('A dynamic execution message for ISSUE passing arg')
expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument'])
expect(dynamic_description_def.condition_block).to be_nil
expect(dynamic_description_def.types).to eq([])
......@@ -100,6 +107,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cc_def.aliases).to eq([])
expect(cc_def.description).to eq('')
expect(cc_def.explanation).to eq('')
expect(cc_def.execution_message).to eq('')
expect(cc_def.params).to eq([])
expect(cc_def.condition_block).to be_nil
expect(cc_def.types).to eq([])
......@@ -111,6 +119,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cond_action_def.aliases).to eq([])
expect(cond_action_def.description).to eq('')
expect(cond_action_def.explanation).to be_a_kind_of(Proc)
expect(cond_action_def.execution_message).to eq('Command applied correctly')
expect(cond_action_def.params).to eq([])
expect(cond_action_def.condition_block).to be_a_kind_of(Proc)
expect(cond_action_def.types).to eq([])
......@@ -122,6 +131,7 @@ describe Gitlab::QuickActions::Dsl do
expect(with_params_parsing_def.aliases).to eq([])
expect(with_params_parsing_def.description).to eq('')
expect(with_params_parsing_def.explanation).to eq('')
expect(with_params_parsing_def.execution_message).to eq('')
expect(with_params_parsing_def.params).to eq([])
expect(with_params_parsing_def.condition_block).to be_nil
expect(with_params_parsing_def.types).to eq([])
......@@ -133,6 +143,7 @@ describe Gitlab::QuickActions::Dsl do
expect(substitution_def.aliases).to eq([])
expect(substitution_def.description).to eq('')
expect(substitution_def.explanation).to eq('')
expect(substitution_def.execution_message).to eq('')
expect(substitution_def.params).to eq(['<Comment>'])
expect(substitution_def.condition_block).to be_nil
expect(substitution_def.types).to eq([])
......@@ -144,6 +155,7 @@ describe Gitlab::QuickActions::Dsl do
expect(has_types.aliases).to eq([])
expect(has_types.description).to eq('A command with types')
expect(has_types.explanation).to eq('')
expect(has_types.execution_message).to eq('')
expect(has_types.params).to eq([])
expect(has_types.condition_block).to be_nil
expect(has_types.types).to eq([Issue, Commit])
......
......@@ -5,7 +5,7 @@ shared_examples 'tag quick action' do
it 'tags this commit' do
add_note("/tag #{tag_name} #{tag_message}")
expect(page).to have_content 'Commands applied'
expect(page).to have_content %{Tagged this commit to #{tag_name} with "#{tag_message}".}
expect(page).to have_content "tagged commit #{truncated_commit_sha}"
expect(page).to have_content tag_name
......
......@@ -68,7 +68,7 @@ shared_examples 'close quick action' do |issuable_type|
it "does not close the #{issuable_type}" do
add_note('/close')
expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content "Closed this #{issuable.to_ability_name.humanize(capitalize: false)}."
expect(issuable).to be_open
end
end
......
......@@ -2,8 +2,14 @@
shared_examples 'create_merge_request quick action' do
context 'create a merge request starting from an issue' do
def expect_mr_quickaction(success)
expect(page).to have_content 'Commands applied'
def expect_mr_quickaction(success, branch_name = nil)
command_message = if branch_name
"Created branch '#{branch_name}' and a merge request to resolve this issue"
else
"Created a branch and a merge request to resolve this issue"
end
expect(page).to have_content command_message
if success
expect(page).to have_content 'created merge request'
......@@ -13,19 +19,21 @@ shared_examples 'create_merge_request quick action' do
end
it "doesn't create a merge request when the branch name is invalid" do
add_note("/create_merge_request invalid branch name")
branch_name = 'invalid branch name'
add_note("/create_merge_request #{branch_name}")
wait_for_requests
expect_mr_quickaction(false)
expect_mr_quickaction(false, branch_name)
end
it "doesn't create a merge request when a branch with that name already exists" do
add_note("/create_merge_request feature")
branch_name = 'feature'
add_note("/create_merge_request #{branch_name}")
wait_for_requests
expect_mr_quickaction(false)
expect_mr_quickaction(false, branch_name)
end
it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do
......@@ -46,7 +54,7 @@ shared_examples 'create_merge_request quick action' do
branch_name = '1-feature'
add_note("/create_merge_request #{branch_name}")
expect_mr_quickaction(true)
expect_mr_quickaction(true, branch_name)
created_mr = project.merge_requests.last
expect(created_mr.source_branch).to eq(branch_name)
......
......@@ -9,7 +9,6 @@ shared_examples 'duplicate quick action' do
add_note("/duplicate ##{original_issue.to_reference}")
expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
expect(page).to have_content 'Commands applied'
expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
expect(issue.reload).to be_closed
......@@ -28,7 +27,6 @@ shared_examples 'duplicate quick action' do
it 'does not create a note, and does not mark the issue as a duplicate' do
add_note("/duplicate ##{original_issue.to_reference}")
expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
expect(issue.reload).to be_open
......
......@@ -12,7 +12,7 @@ shared_examples 'move quick action' do
it 'moves the issue' do
add_note("/move #{target_project.full_path}")
expect(page).to have_content 'Commands applied'
expect(page).to have_content "Moved this issue to #{target_project.full_path}."
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
......@@ -29,7 +29,7 @@ shared_examples 'move quick action' do
wait_for_requests
expect(page).to have_content 'Commands applied'
expect(page).to have_content "Moved this issue to #{project_unauthorized.full_path}."
expect(issue.reload).to be_open
end
end
......@@ -40,7 +40,7 @@ shared_examples 'move quick action' do
wait_for_requests
expect(page).to have_content 'Commands applied'
expect(page).to have_content "Move this issue failed because target project doesn't exists"
expect(issue.reload).to be_open
end
end
......@@ -56,7 +56,7 @@ shared_examples 'move quick action' do
shared_examples 'applies the commands to issues in both projects, target and source' do
it "applies quick actions" do
expect(page).to have_content 'Commands applied'
expect(page).to have_content "Moved this issue to #{target_project.full_path}."
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
......
......@@ -10,7 +10,7 @@ shared_examples 'merge quick action' do
it 'merges the MR' do
add_note("/merge")
expect(page).to have_content 'Commands applied'
expect(page).to have_content 'Scheduled to merge this merge request when the pipeline succeeds.'
expect(merge_request.reload).to be_merged
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment