Commit bf48b071 authored by Sean McGivern's avatar Sean McGivern

Merge branch '50199-quick-actions-refactor' into 'master'

Extend quick actions dsl

Closes #50199

See merge request gitlab-org/gitlab-ce!26095
parents ece9e270 4b9ff4d2
...@@ -4,21 +4,24 @@ module QuickActions ...@@ -4,21 +4,24 @@ module QuickActions
class InterpretService < BaseService class InterpretService < BaseService
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include Gitlab::QuickActions::Dsl include Gitlab::QuickActions::Dsl
include Gitlab::QuickActions::IssueActions
include Gitlab::QuickActions::IssueAndMergeRequestActions
include Gitlab::QuickActions::IssuableActions
include Gitlab::QuickActions::MergeRequestActions
include Gitlab::QuickActions::CommitActions
include Gitlab::QuickActions::CommonActions
attr_reader :issuable attr_reader :quick_action_target
# Counts how many commands have been executed. # Counts how many commands have been executed.
# Used to display relevant feedback on UI when a note # Used to display relevant feedback on UI when a note
# with only commands has been processed. # with only commands has been processed.
attr_accessor :commands_executed_count attr_accessor :commands_executed_count
SHRUG = '¯\\_(ツ)_/¯'.freeze # Takes an quick_action_target and returns an array of all the available commands
TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
# Takes an issuable and returns an array of all the available commands
# represented with .to_h # represented with .to_h
def available_commands(issuable) def available_commands(quick_action_target)
@issuable = issuable @quick_action_target = quick_action_target
self.class.command_definitions.map do |definition| self.class.command_definitions.map do |definition|
next unless definition.available?(self) next unless definition.available?(self)
...@@ -29,10 +32,10 @@ module QuickActions ...@@ -29,10 +32,10 @@ module QuickActions
# Takes a text and interprets the commands that are extracted from it. # 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, and hash of changes to be applied to a record.
def execute(content, issuable, only: nil) 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)
@issuable = issuable @quick_action_target = quick_action_target
@updates = {} @updates = {}
content, commands = extractor.extract_commands(content, only: only) content, commands = extractor.extract_commands(content, only: only)
...@@ -43,10 +46,10 @@ module QuickActions ...@@ -43,10 +46,10 @@ module QuickActions
# Takes a text and interprets the commands that are extracted from it. # Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and array of changes explained. # Returns the content without commands, and array of changes explained.
def explain(content, issuable) def explain(content, quick_action_target)
return [content, []] unless current_user.can?(:use_quick_actions) return [content, []] unless current_user.can?(:use_quick_actions)
@issuable = issuable @quick_action_target = quick_action_target
content, commands = extractor.extract_commands(content) content, commands = extractor.extract_commands(content)
commands = explain_commands(commands) commands = explain_commands(commands)
...@@ -59,601 +62,6 @@ module QuickActions ...@@ -59,601 +62,6 @@ module QuickActions
Gitlab::QuickActions::Extractor.new(self.class.command_definitions) Gitlab::QuickActions::Extractor.new(self.class.command_definitions)
end end
desc do
"Close this #{issuable.to_ability_name.humanize(capitalize: false)}"
end
explanation do
"Closes this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.open? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :close do
@updates[:state_event] = 'close'
end
desc do
"Reopen this #{issuable.to_ability_name.humanize(capitalize: false)}"
end
explanation do
"Reopens this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.closed? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :reopen do
@updates[:state_event] = 'reopen'
end
desc 'Merge (when the pipeline succeeds)'
explanation 'Merges this merge request when the pipeline succeeds.'
condition do
last_diff_sha = params && params[:merge_request_diff_head_sha]
issuable.is_a?(MergeRequest) &&
issuable.persisted? &&
issuable.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
end
command :merge do
@updates[:merge] = params[:merge_request_diff_head_sha]
end
desc 'Change title'
explanation do |title_param|
"Changes the title to \"#{title_param}\"."
end
params '<New title>'
condition do
issuable.persisted? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :title do |title_param|
@updates[:title] = title_param
end
desc 'Assign'
# rubocop: disable CodeReuse/ActiveRecord
explanation do |users|
users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}."
end
# rubocop: enable CodeReuse/ActiveRecord
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
parse_params do |assignee_param|
extract_users(assignee_param)
end
command :assign do |users|
next if users.empty?
if issuable.allows_multiple_assignees?
@updates[:assignee_ids] ||= issuable.assignees.map(&:id)
@updates[:assignee_ids] += users.map(&:id)
else
@updates[:assignee_ids] = [users.first.id]
end
end
desc do
if issuable.allows_multiple_assignees?
'Remove all or specific assignee(s)'
else
'Remove assignee'
end
end
explanation do |users = nil|
assignees = issuable.assignees
assignees &= users if users.present? && issuable.allows_multiple_assignees?
"Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
end
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
parse_params do |unassign_param|
# When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
extract_users(unassign_param) if issuable.allows_multiple_assignees?
end
command :unassign do |users = nil|
if issuable.allows_multiple_assignees? && users&.any?
@updates[:assignee_ids] ||= issuable.assignees.map(&:id)
@updates[:assignee_ids] -= users.map(&:id)
else
@updates[:assignee_ids] = []
end
end
desc 'Set milestone'
explanation do |milestone|
"Sets the milestone to #{milestone.to_reference}." if milestone
end
params '%"milestone"'
condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
find_milestones(project, state: 'active').any?
end
parse_params do |milestone_param|
extract_references(milestone_param, :milestone).first ||
find_milestones(project, title: milestone_param.strip).first
end
command :milestone do |milestone|
@updates[:milestone_id] = milestone.id if milestone
end
desc 'Remove milestone'
explanation do
"Removes #{issuable.milestone.to_reference(format: :name)} milestone."
end
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.milestone_id? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :remove_milestone do
@updates[:milestone_id] = nil
end
desc 'Add label(s)'
explanation do |labels_param|
labels = find_label_references(labels_param)
"Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
end
params '~label1 ~"label 2"'
condition do
parent &&
current_user.can?(:"admin_#{issuable.to_ability_name}", parent) &&
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
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?
else
'Removes all labels.'
end
end
params '~label1 ~"label 2"'
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", parent)
end
command :unlabel do |labels_param = nil|
if labels_param.present?
label_ids = find_label_ids(labels_param)
if label_ids.any?
@updates[:remove_label_ids] ||= []
@updates[:remove_label_ids] += label_ids
@updates[:remove_label_ids].uniq!
end
else
@updates[:label_ids] = []
end
end
desc 'Replace all label(s)'
explanation do |labels_param|
labels = find_label_references(labels_param)
"Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
end
params '~label1 ~"label 2"'
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
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
end
desc 'Copy labels and milestone from other issue or merge request'
explanation do |source_issuable|
"Copy labels and milestone from #{source_issuable.to_reference}."
end
params '#issue | !merge_request'
condition do
[MergeRequest, Issue].include?(issuable.class) &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
parse_params do |issuable_param|
extract_references(issuable_param, :issue).first ||
extract_references(issuable_param, :merge_request).first
end
command :copy_metadata do |source_issuable|
if source_issuable.present? && source_issuable.project.id == issuable.project.id
@updates[:add_label_ids] = source_issuable.labels.map(&:id)
@updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
end
end
desc 'Add a todo'
explanation 'Adds a todo.'
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
!TodoService.new.todo_exist?(issuable, current_user)
end
command :todo do
@updates[:todo_event] = 'add'
end
desc 'Mark todo as done'
explanation 'Marks todo as done.'
condition do
issuable.persisted? &&
TodoService.new.todo_exist?(issuable, current_user)
end
command :done do
@updates[:todo_event] = 'done'
end
desc 'Subscribe'
explanation do
"Subscribes to this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
!issuable.subscribed?(current_user, project)
end
command :subscribe do
@updates[:subscription_event] = 'subscribe'
end
desc 'Unsubscribe'
explanation do
"Unsubscribes from this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.subscribed?(current_user, project)
end
command :unsubscribe do
@updates[:subscription_event] = 'unsubscribe'
end
desc 'Set due date'
explanation do |due_date|
"Sets the due date to #{due_date.to_s(:medium)}." if due_date
end
params '<in 2 days | this Friday | December 31st>'
condition do
issuable.respond_to?(:due_date) &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
parse_params do |due_date_param|
Chronic.parse(due_date_param).try(:to_date)
end
command :due do |due_date|
@updates[:due_date] = due_date if due_date
end
desc 'Remove due date'
explanation 'Removes the due date.'
condition do
issuable.persisted? &&
issuable.respond_to?(:due_date) &&
issuable.due_date? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :remove_due_date do
@updates[:due_date] = nil
end
desc 'Toggle the Work In Progress status'
explanation do
verb = issuable.work_in_progress? ? 'Unmarks' : 'Marks'
noun = issuable.to_ability_name.humanize(capitalize: false)
"#{verb} this #{noun} as Work In Progress."
end
condition do
issuable.respond_to?(:work_in_progress?) &&
# Allow it to mark as WIP on MR creation page _or_ through MR notes.
(issuable.new_record? || current_user.can?(:"update_#{issuable.to_ability_name}", issuable))
end
command :wip do
@updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
end
desc 'Toggle emoji award'
explanation do |name|
"Toggles :#{name}: emoji award." if name
end
params ':emoji:'
condition do
issuable.is_a?(Issuable) &&
issuable.persisted?
end
parse_params do |emoji_param|
match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern)
match[1] if match
end
command :award do |name|
if name && issuable.user_can_award?(current_user)
@updates[:emoji_award] = name
end
end
desc 'Set time estimate'
explanation do |time_estimate|
time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
"Sets time estimate to #{time_estimate}." if time_estimate
end
params '<1w 3d 2h 14m>'
condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
parse_params do |raw_duration|
Gitlab::TimeTrackingFormatter.parse(raw_duration)
end
command :estimate do |time_estimate|
if time_estimate
@updates[:time_estimate] = time_estimate
end
end
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} #{Gitlab::TimeTrackingFormatter.output(value)} spent time."
end
end
params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
condition do
issuable.is_a?(TimeTrackable) &&
current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
end
parse_params do |raw_time_date|
Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
end
command :spend do |time_spent, time_spent_date|
if time_spent
@updates[:spend_time] = {
duration: time_spent,
user_id: current_user.id,
spent_at: time_spent_date
}
end
end
desc 'Remove time estimate'
explanation 'Removes time estimate.'
condition do
issuable.persisted? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :remove_estimate do
@updates[:time_estimate] = 0
end
desc 'Remove spent time'
explanation 'Removes spent time.'
condition do
issuable.persisted? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :remove_time_spent do
@updates[:spend_time] = { duration: :reset, user_id: current_user.id }
end
desc "Append the comment with #{SHRUG}"
params '<Comment>'
substitution :shrug do |comment|
"#{comment} #{SHRUG}"
end
desc "Append the comment with #{TABLEFLIP}"
params '<Comment>'
substitution :tableflip do |comment|
"#{comment} #{TABLEFLIP}"
end
desc "Lock the discussion"
explanation "Locks the discussion"
condition do
[MergeRequest, Issue].include?(issuable.class) &&
issuable.persisted? &&
!issuable.discussion_locked? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
end
command :lock do
@updates[:discussion_locked] = true
end
desc "Unlock the discussion"
explanation "Unlocks the discussion"
condition do
[MergeRequest, Issue].include?(issuable.class) &&
issuable.persisted? &&
issuable.discussion_locked? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
end
command :unlock do
@updates[:discussion_locked] = false
end
# This is a dummy command, so that it appears in the autocomplete commands
desc 'CC'
params '@user'
command :cc
desc 'Set target branch'
explanation do |branch_name|
"Sets target branch to #{branch_name}."
end
params '<Local branch name>'
condition do
issuable.respond_to?(:target_branch) &&
(current_user.can?(:"update_#{issuable.to_ability_name}", issuable) ||
issuable.new_record?)
end
parse_params do |target_branch_param|
target_branch_param.strip
end
command :target_branch do |branch_name|
@updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
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." if label
end
params '~"Target column"'
condition do
issuable.is_a?(Issue) &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable) &&
issuable.project.boards.count == 1
end
command :board_move do |target_list_name|
label_ids = find_label_ids(target_list_name)
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(issuable.project_id).id_in(label_id).exists?
@updates[:remove_label_ids] = issuable
.labels
.on_project_boards(issuable.project_id)
.id_not_in(label_id)
.pluck_primary_key
@updates[:add_label_ids] = [label_id]
end
end
desc 'Mark this issue as a duplicate of another issue'
explanation do |duplicate_reference|
"Marks this issue as a duplicate of #{duplicate_reference}."
end
params '#issue'
condition do
issuable.is_a?(Issue) &&
issuable.persisted? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
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
end
end
desc 'Move this issue to another project.'
explanation do |path_to_project|
"Moves this issue to #{path_to_project}."
end
params 'path/to/project'
condition do
issuable.is_a?(Issue) &&
issuable.persisted? &&
current_user.can?(:"admin_#{issuable.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
end
end
desc 'Make issue confidential.'
explanation do
'Makes this issue confidential'
end
condition do
issuable.is_a?(Issue) && current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
end
command :confidential do
@updates[:confidential] = true
end
desc 'Tag this commit.'
explanation do |tag_name, message|
with_message = %{ with "#{message}"} if message.present?
"Tags this commit to #{tag_name}#{with_message}."
end
params 'v1.2.3 <message>'
parse_params do |tag_name_and_message|
tag_name_and_message.split(' ', 2)
end
condition do
issuable.is_a?(Commit) && current_user.can?(:push_code, project)
end
command :tag do |tag_name, message|
@updates[:tag_name] = tag_name
@updates[:tag_message] = message
end
desc 'Create a merge request.'
explanation do |branch_name = nil|
branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch'
"Creates #{branch_text} and a merge request to resolve this issue"
end
params "<branch name>"
condition do
issuable.is_a?(Issue) && 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: issuable.iid
}
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def extract_users(params) def extract_users(params)
return [] if params.nil? return [] if params.nil?
...@@ -683,7 +91,7 @@ module QuickActions ...@@ -683,7 +91,7 @@ module QuickActions
def group def group
strong_memoize(:group) do strong_memoize(:group) do
issuable.group if issuable.respond_to?(:group) quick_action_target.group if quick_action_target.respond_to?(:group)
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module QuickActions module QuickActions
class CommandDefinition class CommandDefinition
attr_accessor :name, :aliases, :description, :explanation, :params, attr_accessor :name, :aliases, :description, :explanation, :params,
:condition_block, :parse_params_block, :action_block, :warning :condition_block, :parse_params_block, :action_block, :warning, :types
def initialize(name, attributes = {}) def initialize(name, attributes = {})
@name = name @name = name
...@@ -17,6 +17,7 @@ module Gitlab ...@@ -17,6 +17,7 @@ module Gitlab
@condition_block = attributes[:condition_block] @condition_block = attributes[:condition_block]
@parse_params_block = attributes[:parse_params_block] @parse_params_block = attributes[:parse_params_block]
@action_block = attributes[:action_block] @action_block = attributes[:action_block]
@types = attributes[:types] || []
end end
def all_names def all_names
...@@ -28,6 +29,7 @@ module Gitlab ...@@ -28,6 +29,7 @@ module Gitlab
end end
def available?(context) def available?(context)
return false unless valid_type?(context)
return true unless condition_block return true unless condition_block
context.instance_exec(&condition_block) context.instance_exec(&condition_block)
...@@ -96,6 +98,10 @@ module Gitlab ...@@ -96,6 +98,10 @@ module Gitlab
context.instance_exec(arg, &parse_params_block) context.instance_exec(arg, &parse_params_block)
end end
def valid_type?(context)
types.blank? || types.any? { |type| context.quick_action_target.is_a?(type) }
end
end end
end end
end end
# frozen_string_literal: true
module Gitlab
module QuickActions
module CommitActions
extend ActiveSupport::Concern
include Gitlab::QuickActions::Dsl
included do
# Commit only quick actions definitions
desc 'Tag this commit.'
explanation do |tag_name, message|
with_message = %{ with "#{message}"} if message.present?
"Tags this commit to #{tag_name}#{with_message}."
end
params 'v1.2.3 <message>'
parse_params do |tag_name_and_message|
tag_name_and_message.split(' ', 2)
end
types Commit
condition do
current_user.can?(:push_code, project)
end
command :tag do |tag_name, message|
@updates[:tag_name] = tag_name
@updates[:tag_message] = message
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module QuickActions
module CommonActions
extend ActiveSupport::Concern
include Gitlab::QuickActions::Dsl
included do
# This is a dummy command, so that it appears in the autocomplete commands
desc 'CC'
params '@user'
command :cc
end
end
end
end
...@@ -24,7 +24,7 @@ module Gitlab ...@@ -24,7 +24,7 @@ module Gitlab
# Example: # Example:
# #
# desc do # desc do
# "This is a dynamic description for #{noteable.to_ability_name}" # "This is a dynamic description for #{quick_action_target.to_ability_name}"
# end # end
# command :command_key do |arguments| # command :command_key do |arguments|
# # Awesome code block # # Awesome code block
...@@ -66,6 +66,23 @@ module Gitlab ...@@ -66,6 +66,23 @@ module Gitlab
@explanation = block_given? ? block : text @explanation = block_given? ? block : text
end end
# Allows to define type(s) that must be met in order for the command
# to be returned by `.command_names` & `.command_definitions`.
#
# It is being evaluated before the conditions block is being evaluated
#
# If no types are passed then any type is allowed as the check is simply skipped.
#
# Example:
#
# types Commit, Issue, MergeRequest
# command :command_key do |arguments|
# # Awesome code block
# end
def types(*types_list)
@types = types_list
end
# Allows to define conditions that must be met in order for the command # Allows to define conditions that must be met in order for the command
# to be returned by `.command_names` & `.command_definitions`. # to be returned by `.command_names` & `.command_definitions`.
# It accepts a block that will be evaluated with the context # It accepts a block that will be evaluated with the context
...@@ -144,7 +161,8 @@ module Gitlab ...@@ -144,7 +161,8 @@ module Gitlab
params: @params, params: @params,
condition_block: @condition_block, condition_block: @condition_block,
parse_params_block: @parse_params_block, parse_params_block: @parse_params_block,
action_block: block action_block: block,
types: @types
) )
self.command_definitions << definition self.command_definitions << definition
...@@ -159,6 +177,7 @@ module Gitlab ...@@ -159,6 +177,7 @@ module Gitlab
@condition_block = nil @condition_block = nil
@warning = nil @warning = nil
@parse_params_block = nil @parse_params_block = nil
@types = nil
end end
end end
end end
......
# frozen_string_literal: true
module Gitlab
module QuickActions
module IssuableActions
extend ActiveSupport::Concern
include Gitlab::QuickActions::Dsl
SHRUG = '¯\\_(ツ)_/¯'.freeze
TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
included do
# Issue, MergeRequest, Epic: quick actions definitions
desc do
"Close this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
end
explanation do
"Closes this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
end
types Issuable
condition do
quick_action_target.persisted? &&
quick_action_target.open? &&
current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :close do
@updates[:state_event] = 'close'
end
desc do
"Reopen this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
end
explanation do
"Reopens this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
end
types Issuable
condition do
quick_action_target.persisted? &&
quick_action_target.closed? &&
current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :reopen do
@updates[:state_event] = 'reopen'
end
desc 'Change title'
explanation do |title_param|
"Changes the title to \"#{title_param}\"."
end
params '<New title>'
types Issuable
condition do
quick_action_target.persisted? &&
current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :title do |title_param|
@updates[:title] = title_param
end
desc 'Add label(s)'
explanation do |labels_param|
labels = find_label_references(labels_param)
"Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
end
params '~label1 ~"label 2"'
types Issuable
condition do
parent &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) &&
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
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?
else
'Removes all labels.'
end
end
params '~label1 ~"label 2"'
types Issuable
condition do
quick_action_target.persisted? &&
quick_action_target.labels.any? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent)
end
command :unlabel do |labels_param = nil|
if labels_param.present?
label_ids = find_label_ids(labels_param)
if label_ids.any?
@updates[:remove_label_ids] ||= []
@updates[:remove_label_ids] += label_ids
@updates[:remove_label_ids].uniq!
end
else
@updates[:label_ids] = []
end
end
desc 'Replace all label(s)'
explanation do |labels_param|
labels = find_label_references(labels_param)
"Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
end
params '~label1 ~"label 2"'
types Issuable
condition do
quick_action_target.persisted? &&
quick_action_target.labels.any? &&
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
end
desc 'Add a todo'
explanation 'Adds a todo.'
types Issuable
condition do
quick_action_target.persisted? &&
!TodoService.new.todo_exist?(quick_action_target, current_user)
end
command :todo do
@updates[:todo_event] = 'add'
end
desc 'Mark todo as done'
explanation 'Marks todo as done.'
types Issuable
condition do
quick_action_target.persisted? &&
TodoService.new.todo_exist?(quick_action_target, current_user)
end
command :done do
@updates[:todo_event] = 'done'
end
desc 'Subscribe'
explanation do
"Subscribes to this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
end
types Issuable
condition do
quick_action_target.persisted? &&
!quick_action_target.subscribed?(current_user, project)
end
command :subscribe do
@updates[:subscription_event] = 'subscribe'
end
desc 'Unsubscribe'
explanation do
"Unsubscribes from this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
end
types Issuable
condition do
quick_action_target.persisted? &&
quick_action_target.subscribed?(current_user, project)
end
command :unsubscribe do
@updates[:subscription_event] = 'unsubscribe'
end
desc 'Toggle emoji award'
explanation do |name|
"Toggles :#{name}: emoji award." if name
end
params ':emoji:'
types Issuable
condition do
quick_action_target.persisted?
end
parse_params do |emoji_param|
match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern)
match[1] if match
end
command :award do |name|
if name && quick_action_target.user_can_award?(current_user)
@updates[:emoji_award] = name
end
end
desc "Append the comment with #{SHRUG}"
params '<Comment>'
types Issuable
substitution :shrug do |comment|
"#{comment} #{SHRUG}"
end
desc "Append the comment with #{TABLEFLIP}"
params '<Comment>'
types Issuable
substitution :tableflip do |comment|
"#{comment} #{TABLEFLIP}"
end
end
end
end
end
# 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.to_s(:medium)}." 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?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |due_date_param|
Chronic.parse(due_date_param).try(:to_date)
end
command :due do |due_date|
@updates[:due_date] = due_date if due_date
end
desc 'Remove due date'
explanation 'Removes 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?(:"admin_#{quick_action_target.to_ability_name}", project)
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." if label
end
params '~"Target column"'
types Issue
condition do
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)
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?
@updates[:remove_label_ids] =
quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id)
@updates[:add_label_ids] = [label_id]
end
end
# rubocop: enable CodeReuse/ActiveRecord
desc 'Mark this issue as a duplicate of another issue'
explanation do |duplicate_reference|
"Marks this issue as a duplicate of #{duplicate_reference}."
end
params '#issue'
types Issue
condition do
quick_action_target.persisted? &&
current_user.can?(:"update_#{quick_action_target.to_ability_name}", 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
end
end
desc 'Move this issue to another project.'
explanation do |path_to_project|
"Moves this issue to #{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
end
end
desc 'Make issue confidential.'
explanation do
'Makes this issue confidential'
end
types Issue
condition do
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :confidential do
@updates[:confidential] = true
end
desc 'Create a merge request.'
explanation do |branch_name = nil|
branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch'
"Creates #{branch_text} and a merge request to resolve this issue"
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
end
end
end
end
# frozen_string_literal: true
module Gitlab
module QuickActions
module IssueAndMergeRequestActions
extend ActiveSupport::Concern
include Gitlab::QuickActions::Dsl
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}."
end
# rubocop: enable CodeReuse/ActiveRecord
params do
quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
types Issue, MergeRequest
condition do
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |assignee_param|
extract_users(assignee_param)
end
command :assign do |users|
next if users.empty?
if quick_action_target.allows_multiple_assignees?
@updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
@updates[:assignee_ids] += users.map(&:id)
else
@updates[:assignee_ids] = [users.first.id]
end
end
desc do
if quick_action_target.allows_multiple_assignees?
'Remove all or specific assignee(s)'
else
'Remove assignee'
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}."
end
params do
quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : ''
end
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
quick_action_target.assignees.any? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |unassign_param|
# When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
extract_users(unassign_param) if quick_action_target.allows_multiple_assignees?
end
command :unassign do |users = nil|
if quick_action_target.allows_multiple_assignees? && users&.any?
@updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
@updates[:assignee_ids] -= users.map(&:id)
else
@updates[:assignee_ids] = []
end
end
desc 'Set milestone'
explanation do |milestone|
"Sets the milestone to #{milestone.to_reference}." if milestone
end
params '%"milestone"'
types Issue, MergeRequest
condition do
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) &&
find_milestones(project, state: 'active').any?
end
parse_params do |milestone_param|
extract_references(milestone_param, :milestone).first ||
find_milestones(project, title: milestone_param.strip).first
end
command :milestone do |milestone|
@updates[:milestone_id] = milestone.id if milestone
end
desc 'Remove milestone'
explanation do
"Removes #{quick_action_target.milestone.to_reference(format: :name)} milestone."
end
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
quick_action_target.milestone_id? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
command :remove_milestone do
@updates[:milestone_id] = nil
end
desc 'Copy labels and milestone from other issue or merge request'
explanation do |source_issuable|
"Copy labels and milestone from #{source_issuable.to_reference}."
end
params '#issue | !merge_request'
types Issue, MergeRequest
condition do
current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
end
parse_params do |issuable_param|
extract_references(issuable_param, :issue).first ||
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
@updates[:add_label_ids] = source_issuable.labels.map(&:id)
@updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
end
end
desc 'Set time estimate'
explanation do |time_estimate|
time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
"Sets time estimate to #{time_estimate}." if time_estimate
end
params '<1w 3d 2h 14m>'
types Issue, MergeRequest
condition do
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |raw_duration|
Gitlab::TimeTrackingFormatter.parse(raw_duration)
end
command :estimate do |time_estimate|
if time_estimate
@updates[:time_estimate] = time_estimate
end
end
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} #{Gitlab::TimeTrackingFormatter.output(value)} spent time."
end
end
params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
types Issue, MergeRequest
condition do
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
parse_params do |raw_time_date|
Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
end
command :spend do |time_spent, time_spent_date|
if time_spent
@updates[:spend_time] = {
duration: time_spent,
user_id: current_user.id,
spent_at: time_spent_date
}
end
end
desc 'Remove time estimate'
explanation 'Removes time estimate.'
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
command :remove_estimate do
@updates[:time_estimate] = 0
end
desc 'Remove spent time'
explanation 'Removes spent time.'
condition do
quick_action_target.persisted? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
types Issue, MergeRequest
command :remove_time_spent do
@updates[:spend_time] = { duration: :reset, user_id: current_user.id }
end
desc "Lock the discussion"
explanation "Locks the discussion"
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
!quick_action_target.discussion_locked? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :lock do
@updates[:discussion_locked] = true
end
desc "Unlock the discussion"
explanation "Unlocks the discussion"
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
quick_action_target.discussion_locked? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
command :unlock do
@updates[:discussion_locked] = false
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module QuickActions
module MergeRequestActions
extend ActiveSupport::Concern
include Gitlab::QuickActions::Dsl
included do
# MergeRequest only quick actions definitions
desc 'Merge (when the pipeline succeeds)'
explanation 'Merges this merge request when the pipeline succeeds.'
types MergeRequest
condition do
last_diff_sha = params && params[:merge_request_diff_head_sha]
quick_action_target.persisted? &&
quick_action_target.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
end
command :merge do
@updates[:merge] = params[:merge_request_diff_head_sha]
end
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."
end
types MergeRequest
condition do
quick_action_target.respond_to?(:work_in_progress?) &&
# Allow it to mark as WIP on MR creation page _or_ through MR notes.
(quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target))
end
command :wip do
@updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip'
end
desc 'Set target branch'
explanation do |branch_name|
"Sets target branch to #{branch_name}."
end
params '<Local branch name>'
types MergeRequest
condition do
quick_action_target.respond_to?(:target_branch) &&
(current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) ||
quick_action_target.new_record?)
end
parse_params do |target_branch_param|
target_branch_param.strip
end
command :target_branch do |branch_name|
@updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
end
end
end
end
end
...@@ -3,8 +3,41 @@ require 'rails_helper' ...@@ -3,8 +3,41 @@ require 'rails_helper'
describe 'Issues > User uses quick actions', :js do describe 'Issues > User uses quick actions', :js do
include Spec::Support::Helpers::Features::NotesHelpers include Spec::Support::Helpers::Features::NotesHelpers
it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do context "issuable common quick actions" do
let(:new_url_opts) { {} }
let(:maintainer) { create(:user) }
let(:project) { create(:project, :public) }
let!(:label_bug) { create(:label, project: project, title: 'bug') }
let!(:label_feature) { create(:label, project: project, title: 'feature') }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let(:issuable) { create(:issue, project: project) } let(:issuable) { create(:issue, project: project) }
let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])}
it_behaves_like 'assign quick action', :issue
it_behaves_like 'unassign quick action', :issue
it_behaves_like 'close quick action', :issue
it_behaves_like 'reopen quick action', :issue
it_behaves_like 'title quick action', :issue
it_behaves_like 'todo quick action', :issue
it_behaves_like 'done quick action', :issue
it_behaves_like 'subscribe quick action', :issue
it_behaves_like 'unsubscribe quick action', :issue
it_behaves_like 'lock quick action', :issue
it_behaves_like 'unlock quick action', :issue
it_behaves_like 'milestone quick action', :issue
it_behaves_like 'remove_milestone quick action', :issue
it_behaves_like 'label quick action', :issue
it_behaves_like 'unlabel quick action', :issue
it_behaves_like 'relabel quick action', :issue
it_behaves_like 'award quick action', :issue
it_behaves_like 'estimate quick action', :issue
it_behaves_like 'remove_estimate quick action', :issue
it_behaves_like 'spend quick action', :issue
it_behaves_like 'remove_time_spent quick action', :issue
it_behaves_like 'shrug quick action', :issue
it_behaves_like 'tableflip quick action', :issue
it_behaves_like 'copy_metadata quick action', :issue
it_behaves_like 'issuable time tracker', :issue
end end
describe 'issue-only commands' do describe 'issue-only commands' do
...@@ -15,37 +48,17 @@ describe 'Issues > User uses quick actions', :js do ...@@ -15,37 +48,17 @@ describe 'Issues > User uses quick actions', :js do
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_all_requests
end end
after do after do
wait_for_requests wait_for_requests
end end
describe 'time tracking' do
let(:issue) { create(:issue, project: project) }
before do
visit project_issue_path(project, issue)
end
it_behaves_like 'issuable time tracker'
end
describe 'adding a due date from note' do describe 'adding a due date from note' do
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
context 'when the current user can update the due date' do it_behaves_like 'due quick action available and date can be added'
it 'does not create a note, and sets the due date accordingly' do
add_note("/due 2016-08-28")
expect(page).not_to have_content '/due 2016-08-28'
expect(page).to have_content 'Commands applied'
issue.reload
expect(issue.due_date).to eq Date.new(2016, 8, 28)
end
end
context 'when the current user cannot update the due date' do context 'when the current user cannot update the due date' do
let(:guest) { create(:user) } let(:guest) { create(:user) }
...@@ -56,35 +69,14 @@ describe 'Issues > User uses quick actions', :js do ...@@ -56,35 +69,14 @@ describe 'Issues > User uses quick actions', :js do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
end end
it 'does not create a note, and sets the due date accordingly' do it_behaves_like 'due quick action not available'
add_note("/due 2016-08-28")
expect(page).not_to have_content 'Commands applied'
issue.reload
expect(issue.due_date).to be_nil
end
end end
end end
describe 'removing a due date from note' do describe 'removing a due date from note' do
let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) } let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) }
context 'when the current user can update the due date' do it_behaves_like 'remove_due_date action available and due date can be removed'
it 'does not create a note, and removes the due date accordingly' do
expect(issue.due_date).to eq Date.new(2016, 8, 28)
add_note("/remove_due_date")
expect(page).not_to have_content '/remove_due_date'
expect(page).to have_content 'Commands applied'
issue.reload
expect(issue.due_date).to be_nil
end
end
context 'when the current user cannot update the due date' do context 'when the current user cannot update the due date' do
let(:guest) { create(:user) } let(:guest) { create(:user) }
...@@ -95,15 +87,7 @@ describe 'Issues > User uses quick actions', :js do ...@@ -95,15 +87,7 @@ describe 'Issues > User uses quick actions', :js do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
end end
it 'does not create a note, and sets the due date accordingly' do it_behaves_like 'remove_due_date action not available'
add_note("/remove_due_date")
expect(page).not_to have_content 'Commands applied'
issue.reload
expect(issue.due_date).to eq Date.new(2016, 8, 28)
end
end end
end end
...@@ -200,6 +184,7 @@ describe 'Issues > User uses quick actions', :js do ...@@ -200,6 +184,7 @@ describe 'Issues > User uses quick actions', :js do
gitlab_sign_out gitlab_sign_out
sign_in(user) sign_in(user)
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
end end
it 'moves the issue' do it 'moves the issue' do
...@@ -221,6 +206,7 @@ describe 'Issues > User uses quick actions', :js do ...@@ -221,6 +206,7 @@ describe 'Issues > User uses quick actions', :js do
gitlab_sign_out gitlab_sign_out
sign_in(user) sign_in(user)
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
end end
it 'does not move the issue' do it 'does not move the issue' do
...@@ -238,6 +224,7 @@ describe 'Issues > User uses quick actions', :js do ...@@ -238,6 +224,7 @@ describe 'Issues > User uses quick actions', :js do
gitlab_sign_out gitlab_sign_out
sign_in(user) sign_in(user)
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
end end
it 'does not move the issue' do it 'does not move the issue' do
......
...@@ -9,9 +9,41 @@ describe 'Merge request > User uses quick actions', :js do ...@@ -9,9 +9,41 @@ describe 'Merge request > User uses quick actions', :js do
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do context "issuable common quick actions" do
let!(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
let(:maintainer) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let!(:label_bug) { create(:label, project: project, title: 'bug') }
let!(:label_feature) { create(:label, project: project, title: 'feature') }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let(:issuable) { create(:merge_request, source_project: project) } let(:issuable) { create(:merge_request, source_project: project) }
let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } } let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])}
it_behaves_like 'assign quick action', :merge_request
it_behaves_like 'unassign quick action', :merge_request
it_behaves_like 'close quick action', :merge_request
it_behaves_like 'reopen quick action', :merge_request
it_behaves_like 'title quick action', :merge_request
it_behaves_like 'todo quick action', :merge_request
it_behaves_like 'done quick action', :merge_request
it_behaves_like 'subscribe quick action', :merge_request
it_behaves_like 'unsubscribe quick action', :merge_request
it_behaves_like 'lock quick action', :merge_request
it_behaves_like 'unlock quick action', :merge_request
it_behaves_like 'milestone quick action', :merge_request
it_behaves_like 'remove_milestone quick action', :merge_request
it_behaves_like 'label quick action', :merge_request
it_behaves_like 'unlabel quick action', :merge_request
it_behaves_like 'relabel quick action', :merge_request
it_behaves_like 'award quick action', :merge_request
it_behaves_like 'estimate quick action', :merge_request
it_behaves_like 'remove_estimate quick action', :merge_request
it_behaves_like 'spend quick action', :merge_request
it_behaves_like 'remove_time_spent quick action', :merge_request
it_behaves_like 'shrug quick action', :merge_request
it_behaves_like 'tableflip quick action', :merge_request
it_behaves_like 'copy_metadata quick action', :merge_request
it_behaves_like 'issuable time tracker', :merge_request
end end
describe 'merge-request-only commands' do describe 'merge-request-only commands' do
...@@ -24,20 +56,12 @@ describe 'Merge request > User uses quick actions', :js do ...@@ -24,20 +56,12 @@ describe 'Merge request > User uses quick actions', :js do
project.add_maintainer(user) project.add_maintainer(user)
end end
describe 'time tracking' do
before do
sign_in(user)
visit project_merge_request_path(project, merge_request)
end
it_behaves_like 'issuable time tracker'
end
describe 'toggling the WIP prefix in the title from note' do describe 'toggling the WIP prefix in the title from note' do
context 'when the current user can toggle the WIP prefix' do context 'when the current user can toggle the WIP prefix' do
before do before do
sign_in(user) sign_in(user)
visit project_merge_request_path(project, merge_request) visit project_merge_request_path(project, merge_request)
wait_for_requests
end end
it 'adds the WIP: prefix to the title' do it 'adds the WIP: prefix to the title' do
...@@ -135,11 +159,16 @@ describe 'Merge request > User uses quick actions', :js do ...@@ -135,11 +159,16 @@ describe 'Merge request > User uses quick actions', :js do
visit project_merge_request_path(project, merge_request) visit project_merge_request_path(project, merge_request)
end end
it 'does not recognize the command nor create a note' do it_behaves_like 'due quick action not available'
add_note('/due 2016-08-28') end
expect(page).not_to have_content '/due 2016-08-28' describe 'removing a due date from note' do
before do
sign_in(user)
visit project_merge_request_path(project, merge_request)
end end
it_behaves_like 'remove_due_date action not available'
end end
describe '/target_branch command in merge request' do describe '/target_branch command in merge request' do
......
...@@ -69,6 +69,36 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -69,6 +69,36 @@ describe Gitlab::QuickActions::CommandDefinition do
expect(subject.available?(opts)).to be true expect(subject.available?(opts)).to be true
end end
end end
context "when the command has types" do
before do
subject.types = [Issue, Commit]
end
context "when the command target type is allowed" do
it "returns true" do
opts[:quick_action_target] = Issue.new
expect(subject.available?(opts)).to be true
end
end
context "when the command target type is not allowed" do
it "returns true" do
opts[:quick_action_target] = MergeRequest.new
expect(subject.available?(opts)).to be false
end
end
end
context "when the command has no types" do
it "any target type is allowed" do
opts[:quick_action_target] = Issue.new
expect(subject.available?(opts)).to be true
opts[:quick_action_target] = MergeRequest.new
expect(subject.available?(opts)).to be true
end
end
end end
describe "#execute" do describe "#execute" do
......
...@@ -48,13 +48,19 @@ describe Gitlab::QuickActions::Dsl do ...@@ -48,13 +48,19 @@ describe Gitlab::QuickActions::Dsl do
substitution :something do |text| substitution :something do |text|
"#{text} Some complicated thing you want in here" "#{text} Some complicated thing you want in here"
end end
desc 'A command with types'
types Issue, Commit
command :has_types do
"Has Issue and Commit types"
end
end end
end end
describe '.command_definitions' do describe '.command_definitions' do
it 'returns an array with commands definitions' do it 'returns an array with commands definitions' do
no_args_def, explanation_with_aliases_def, dynamic_description_def, no_args_def, explanation_with_aliases_def, dynamic_description_def,
cc_def, cond_action_def, with_params_parsing_def, substitution_def = cc_def, cond_action_def, with_params_parsing_def, substitution_def, has_types =
DummyClass.command_definitions DummyClass.command_definitions
expect(no_args_def.name).to eq(:no_args) expect(no_args_def.name).to eq(:no_args)
...@@ -63,6 +69,7 @@ describe Gitlab::QuickActions::Dsl do ...@@ -63,6 +69,7 @@ describe Gitlab::QuickActions::Dsl do
expect(no_args_def.explanation).to eq('') expect(no_args_def.explanation).to eq('')
expect(no_args_def.params).to eq([]) expect(no_args_def.params).to eq([])
expect(no_args_def.condition_block).to be_nil expect(no_args_def.condition_block).to be_nil
expect(no_args_def.types).to eq([])
expect(no_args_def.action_block).to be_a_kind_of(Proc) expect(no_args_def.action_block).to be_a_kind_of(Proc)
expect(no_args_def.parse_params_block).to be_nil expect(no_args_def.parse_params_block).to be_nil
expect(no_args_def.warning).to eq('') expect(no_args_def.warning).to eq('')
...@@ -73,6 +80,7 @@ describe Gitlab::QuickActions::Dsl do ...@@ -73,6 +80,7 @@ describe Gitlab::QuickActions::Dsl do
expect(explanation_with_aliases_def.explanation).to eq('Static explanation') expect(explanation_with_aliases_def.explanation).to eq('Static explanation')
expect(explanation_with_aliases_def.params).to eq(['The first argument']) 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.condition_block).to be_nil
expect(explanation_with_aliases_def.types).to eq([])
expect(explanation_with_aliases_def.action_block).to be_a_kind_of(Proc) expect(explanation_with_aliases_def.action_block).to be_a_kind_of(Proc)
expect(explanation_with_aliases_def.parse_params_block).to be_nil expect(explanation_with_aliases_def.parse_params_block).to be_nil
expect(explanation_with_aliases_def.warning).to eq('Possible problem!') expect(explanation_with_aliases_def.warning).to eq('Possible problem!')
...@@ -83,6 +91,7 @@ describe Gitlab::QuickActions::Dsl do ...@@ -83,6 +91,7 @@ describe Gitlab::QuickActions::Dsl do
expect(dynamic_description_def.explanation).to eq('') expect(dynamic_description_def.explanation).to eq('')
expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument']) 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.condition_block).to be_nil
expect(dynamic_description_def.types).to eq([])
expect(dynamic_description_def.action_block).to be_a_kind_of(Proc) expect(dynamic_description_def.action_block).to be_a_kind_of(Proc)
expect(dynamic_description_def.parse_params_block).to be_nil expect(dynamic_description_def.parse_params_block).to be_nil
expect(dynamic_description_def.warning).to eq('') expect(dynamic_description_def.warning).to eq('')
...@@ -93,6 +102,7 @@ describe Gitlab::QuickActions::Dsl do ...@@ -93,6 +102,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cc_def.explanation).to eq('') expect(cc_def.explanation).to eq('')
expect(cc_def.params).to eq([]) expect(cc_def.params).to eq([])
expect(cc_def.condition_block).to be_nil expect(cc_def.condition_block).to be_nil
expect(cc_def.types).to eq([])
expect(cc_def.action_block).to be_nil expect(cc_def.action_block).to be_nil
expect(cc_def.parse_params_block).to be_nil expect(cc_def.parse_params_block).to be_nil
expect(cc_def.warning).to eq('') expect(cc_def.warning).to eq('')
...@@ -103,6 +113,7 @@ describe Gitlab::QuickActions::Dsl do ...@@ -103,6 +113,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cond_action_def.explanation).to be_a_kind_of(Proc) expect(cond_action_def.explanation).to be_a_kind_of(Proc)
expect(cond_action_def.params).to eq([]) expect(cond_action_def.params).to eq([])
expect(cond_action_def.condition_block).to be_a_kind_of(Proc) expect(cond_action_def.condition_block).to be_a_kind_of(Proc)
expect(cond_action_def.types).to eq([])
expect(cond_action_def.action_block).to be_a_kind_of(Proc) expect(cond_action_def.action_block).to be_a_kind_of(Proc)
expect(cond_action_def.parse_params_block).to be_nil expect(cond_action_def.parse_params_block).to be_nil
expect(cond_action_def.warning).to eq('') expect(cond_action_def.warning).to eq('')
...@@ -113,6 +124,7 @@ describe Gitlab::QuickActions::Dsl do ...@@ -113,6 +124,7 @@ describe Gitlab::QuickActions::Dsl do
expect(with_params_parsing_def.explanation).to eq('') expect(with_params_parsing_def.explanation).to eq('')
expect(with_params_parsing_def.params).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.condition_block).to be_nil
expect(with_params_parsing_def.types).to eq([])
expect(with_params_parsing_def.action_block).to be_a_kind_of(Proc) expect(with_params_parsing_def.action_block).to be_a_kind_of(Proc)
expect(with_params_parsing_def.parse_params_block).to be_a_kind_of(Proc) expect(with_params_parsing_def.parse_params_block).to be_a_kind_of(Proc)
expect(with_params_parsing_def.warning).to eq('') expect(with_params_parsing_def.warning).to eq('')
...@@ -123,9 +135,21 @@ describe Gitlab::QuickActions::Dsl do ...@@ -123,9 +135,21 @@ describe Gitlab::QuickActions::Dsl do
expect(substitution_def.explanation).to eq('') expect(substitution_def.explanation).to eq('')
expect(substitution_def.params).to eq(['<Comment>']) expect(substitution_def.params).to eq(['<Comment>'])
expect(substitution_def.condition_block).to be_nil expect(substitution_def.condition_block).to be_nil
expect(substitution_def.types).to eq([])
expect(substitution_def.action_block.call('text')).to eq('text Some complicated thing you want in here') expect(substitution_def.action_block.call('text')).to eq('text Some complicated thing you want in here')
expect(substitution_def.parse_params_block).to be_nil expect(substitution_def.parse_params_block).to be_nil
expect(substitution_def.warning).to eq('') expect(substitution_def.warning).to eq('')
expect(has_types.name).to eq(:has_types)
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.params).to eq([])
expect(has_types.condition_block).to be_nil
expect(has_types.types).to eq([Issue, Commit])
expect(has_types.action_block).to be_a_kind_of(Proc)
expect(has_types.parse_params_block).to be_nil
expect(has_types.warning).to eq('')
end end
end end
end end
# Specifications for behavior common to all objects with executable attributes.
# It takes a `issuable_type`, and expect an `issuable`.
shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type|
include Spec::Support::Helpers::Features::NotesHelpers
let(:maintainer) { create(:user) }
let(:project) do
case issuable_type
when :merge_request
create(:project, :public, :repository)
when :issue
create(:project, :public)
end
end
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let!(:label_bug) { create(:label, project: project, title: 'bug') }
let!(:label_feature) { create(:label, project: project, title: 'feature') }
let(:new_url_opts) { {} }
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
after do
# Ensure all outstanding Ajax requests are complete to avoid database deadlocks
wait_for_requests
end
describe "new #{issuable_type}", :js do
context 'with commands in the description' do
it "creates the #{issuable_type} and interpret commands accordingly" do
case issuable_type
when :merge_request
visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts)
when :issue
visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts)
end
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\""
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq "bug description"
expect(issuable.labels).to eq [label_bug]
expect(issuable.milestone).to eq milestone
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
end
end
end
describe "note on #{issuable_type}", :js do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
end
context 'with a note containing commands' do
it 'creates a note without the commands and interpret the commands accordingly' do
assignee = create(:user, username: 'bob')
add_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
expect(page).to have_content 'Awesome!'
expect(page).not_to have_content '/assign @bob'
expect(page).not_to have_content '/label ~bug'
expect(page).not_to have_content '/milestone %"ASAP"'
wait_for_requests
issuable.reload
note = issuable.notes.user.first
expect(note.note).to eq "Awesome!"
expect(issuable.assignees).to eq [assignee]
expect(issuable.labels).to eq [label_bug]
expect(issuable.milestone).to eq milestone
end
it 'removes the quick action from note and explains it in the preview' do
preview_note("Awesome!\n\n/close")
expect(page).to have_content 'Awesome!'
expect(page).not_to have_content '/close'
issuable_name = issuable.is_a?(Issue) ? 'issue' : 'merge request'
expect(page).to have_content "Closes this #{issuable_name}."
end
end
context 'with a note containing only commands' do
it 'does not create a note but interpret the commands accordingly' do
assignee = create(:user, username: 'bob')
add_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
expect(page).not_to have_content '/assign @bob'
expect(page).not_to have_content '/label ~bug'
expect(page).not_to have_content '/milestone %"ASAP"'
expect(page).to have_content 'Commands applied'
issuable.reload
expect(issuable.notes.user).to be_empty
expect(issuable.assignees).to eq [assignee]
expect(issuable.labels).to eq [label_bug]
expect(issuable.milestone).to eq milestone
end
end
context "with a note closing the #{issuable_type}" do
before do
expect(issuable).to be_open
end
context "when current user can close #{issuable_type}" do
it "closes the #{issuable_type}" do
add_note("/close")
expect(page).not_to have_content '/close'
expect(page).to have_content 'Commands applied'
expect(issuable.reload).to be_closed
end
end
context "when current user cannot close #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not close the #{issuable_type}" do
add_note("/close")
expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_open
end
end
end
context "with a note reopening the #{issuable_type}" do
before do
issuable.close
expect(issuable).to be_closed
end
context "when current user can reopen #{issuable_type}" do
it "reopens the #{issuable_type}" do
add_note("/reopen")
expect(page).not_to have_content '/reopen'
expect(page).to have_content 'Commands applied'
expect(issuable.reload).to be_open
end
end
context "when current user cannot reopen #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not reopen the #{issuable_type}" do
add_note("/reopen")
expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_closed
end
end
end
context "with a note changing the #{issuable_type}'s title" do
context "when current user can change title of #{issuable_type}" do
it "reopens the #{issuable_type}" do
add_note("/title Awesome new title")
expect(page).not_to have_content '/title'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.title).to eq 'Awesome new title'
end
end
context "when current user cannot change title of #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not change the #{issuable_type} title" do
add_note("/title Awesome new title")
expect(page).not_to have_content 'Commands applied'
expect(issuable.reload.title).not_to eq 'Awesome new title'
end
end
end
context "with a note marking the #{issuable_type} as todo" do
it "creates a new todo for the #{issuable_type}" do
add_note("/todo")
expect(page).not_to have_content '/todo'
expect(page).to have_content 'Commands applied'
todos = TodosFinder.new(maintainer).execute
todo = todos.first
expect(todos.size).to eq 1
expect(todo).to be_pending
expect(todo.target).to eq issuable
expect(todo.author).to eq maintainer
expect(todo.user).to eq maintainer
end
end
context "with a note marking the #{issuable_type} as done" do
before do
TodoService.new.mark_todo(issuable, maintainer)
end
it "creates a new todo for the #{issuable_type}" do
todos = TodosFinder.new(maintainer).execute
todo = todos.first
expect(todos.size).to eq 1
expect(todos.first).to be_pending
expect(todo.target).to eq issuable
expect(todo.author).to eq maintainer
expect(todo.user).to eq maintainer
add_note("/done")
expect(page).not_to have_content '/done'
expect(page).to have_content 'Commands applied'
expect(todo.reload).to be_done
end
end
context "with a note subscribing to the #{issuable_type}" do
it "creates a new todo for the #{issuable_type}" do
expect(issuable.subscribed?(maintainer, project)).to be_falsy
add_note("/subscribe")
expect(page).not_to have_content '/subscribe'
expect(page).to have_content 'Commands applied'
expect(issuable.subscribed?(maintainer, project)).to be_truthy
end
end
context "with a note unsubscribing to the #{issuable_type} as done" do
before do
issuable.subscribe(maintainer, project)
end
it "creates a new todo for the #{issuable_type}" do
expect(issuable.subscribed?(maintainer, project)).to be_truthy
add_note("/unsubscribe")
expect(page).not_to have_content '/unsubscribe'
expect(page).to have_content 'Commands applied'
expect(issuable.subscribed?(maintainer, project)).to be_falsy
end
end
context "with a note assigning the #{issuable_type} to the current user" do
it "assigns the #{issuable_type} to the current user" do
add_note("/assign me")
expect(page).not_to have_content '/assign me'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.assignees).to eq [maintainer]
end
end
context "with a note locking the #{issuable_type} discussion" do
before do
issuable.update(discussion_locked: false)
expect(issuable).not_to be_discussion_locked
end
context "when current user can lock #{issuable_type} discussion" do
it "locks the #{issuable_type} discussion" do
add_note("/lock")
expect(page).not_to have_content '/lock'
expect(page).to have_content 'Commands applied'
expect(issuable.reload).to be_discussion_locked
end
end
context "when current user cannot lock #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not lock the #{issuable_type} discussion" do
add_note("/lock")
expect(page).not_to have_content 'Commands applied'
expect(issuable).not_to be_discussion_locked
end
end
end
context "with a note unlocking the #{issuable_type} discussion" do
before do
issuable.update(discussion_locked: true)
expect(issuable).to be_discussion_locked
end
context "when current user can unlock #{issuable_type} discussion" do
it "unlocks the #{issuable_type} discussion" do
add_note("/unlock")
expect(page).not_to have_content '/unlock'
expect(page).to have_content 'Commands applied'
expect(issuable.reload).not_to be_discussion_locked
end
end
context "when current user cannot unlock #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not unlock the #{issuable_type} discussion" do
add_note("/unlock")
expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_discussion_locked
end
end
end
end
describe "preview of note on #{issuable_type}", :js do
it 'removes quick actions from note and explains them' do
create(:user, username: 'bob')
visit public_send("project_#{issuable_type}_path", project, issuable)
page.within('.js-main-target-form') do
fill_in 'note[note]', with: "Awesome!\n/assign @bob "
click_on 'Preview'
expect(page).to have_content 'Awesome!'
expect(page).not_to have_content '/assign @bob'
expect(page).to have_content 'Assigns @bob.'
end
end
end
end
# frozen_string_literal: true
shared_examples 'tag quick action' do
end
# frozen_string_literal: true
shared_examples 'assign quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets assign quick action accordingly" do
assignee = create(:user, username: 'bob')
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/assign @bob"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable.assignees).to eq [assignee]
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
end
it "creates the #{issuable_type} and interprets assign quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/assign me"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable.assignees).to eq [maintainer]
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the assign quick action accordingly' do
assignee = create(:user, username: 'bob')
add_note("Awesome!\n\n/assign @bob")
expect(page).to have_content 'Awesome!'
expect(page).not_to have_content '/assign @bob'
wait_for_requests
issuable.reload
note = issuable.notes.user.first
expect(note.note).to eq 'Awesome!'
expect(issuable.assignees).to eq [assignee]
end
it "assigns the #{issuable_type} to the current user" do
add_note("/assign me")
expect(page).not_to have_content '/assign me'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.assignees).to eq [maintainer]
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains assign quick action to bob' do
create(:user, username: 'bob')
visit public_send("project_#{issuable_type}_path", project, issuable)
page.within('.js-main-target-form') do
fill_in 'note[note]', with: "Awesome!\n/assign @bob "
click_on 'Preview'
expect(page).not_to have_content '/assign @bob'
expect(page).to have_content 'Awesome!'
expect(page).to have_content 'Assigns @bob.'
end
end
it 'explains assign quick action to me' do
visit public_send("project_#{issuable_type}_path", project, issuable)
page.within('.js-main-target-form') do
fill_in 'note[note]', with: "Awesome!\n/assign me"
click_on 'Preview'
expect(page).not_to have_content '/assign me'
expect(page).to have_content 'Awesome!'
expect(page).to have_content "Assigns @#{maintainer.username}."
end
end
end
end
# frozen_string_literal: true
shared_examples 'award quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets award quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/award :100:"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.award_emoji).to eq []
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
expect(issuable.award_emoji).to eq []
end
it 'creates the note and interprets the award quick action accordingly' do
add_note("/award :100:")
wait_for_requests
expect(page).not_to have_content '/award'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.award_emoji.last.name).to eq('100')
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains label quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/award :100:')
expect(page).not_to have_content '/award'
expect(page).to have_selector "gl-emoji[data-name='100']"
end
end
end
# frozen_string_literal: true
shared_examples 'close quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets close quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/close"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
end
end
context "post note to existing #{issuable_type}" do
before do
expect(issuable).to be_opened
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the close quick action accordingly' do
add_note("this is done, close\n\n/close")
wait_for_requests
expect(page).not_to have_content '/close'
expect(page).to have_content 'this is done, close'
issuable.reload
note = issuable.notes.user.first
expect(note.note).to eq 'this is done, close'
expect(issuable).to be_closed
end
context "when current user cannot close #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not close the #{issuable_type}" do
add_note('/close')
expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_open
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains close quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
page.within('.js-main-target-form') do
fill_in 'note[note]', with: "this is done, close\n/close"
click_on 'Preview'
expect(page).not_to have_content '/close'
expect(page).to have_content 'this is done, close'
expect(page).to have_content "Closes this #{issuable_type.to_s.humanize.downcase}."
end
end
end
end
# frozen_string_literal: true
shared_examples 'copy_metadata quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets copy_metadata quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/copy_metadata #{source_issuable.to_reference(project)}"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).last
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
issuable.reload
expect(issuable.description).to eq 'bug description'
expect(issuable.milestone).to eq milestone
expect(issuable.labels).to match_array([label_bug, label_feature])
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets copy_metadata quick action accordingly' do
add_note("/copy_metadata #{source_issuable.to_reference(project)}")
wait_for_requests
expect(page).not_to have_content '/copy_metadata'
expect(page).to have_content 'Commands applied'
issuable.reload
expect(issuable.milestone).to eq milestone
expect(issuable.labels).to match_array([label_bug, label_feature])
end
context "when current user cannot copy_metadata" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not copy_metadata' do
add_note("/copy_metadata #{source_issuable.to_reference(project)}")
wait_for_requests
expect(page).not_to have_content '/copy_metadata'
expect(page).not_to have_content 'Commands applied'
issuable.reload
expect(issuable.milestone).not_to eq milestone
expect(issuable.labels).to eq []
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains copy_metadata quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note("/copy_metadata #{source_issuable.to_reference(project)}")
expect(page).not_to have_content '/copy_metadata'
expect(page).to have_content "Copy labels and milestone from #{source_issuable.to_reference(project)}."
end
end
end
# frozen_string_literal: true
shared_examples 'done quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets done quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/done"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
todos = TodosFinder.new(maintainer).execute
expect(todos.size).to eq 0
end
end
context "post note to existing #{issuable_type}" do
before do
TodoService.new.mark_todo(issuable, maintainer)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the done quick action accordingly' do
todos = TodosFinder.new(maintainer).execute
todo = todos.first
expect(todo.reload).to be_pending
expect(todos.size).to eq 1
expect(todo.target).to eq issuable
expect(todo.author).to eq maintainer
expect(todo.user).to eq maintainer
add_note('/done')
wait_for_requests
expect(page).not_to have_content '/done'
expect(page).to have_content 'Commands applied'
expect(todo.reload).to be_done
end
context "when current user cannot mark #{issuable_type} todo as done" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it "does not set the #{issuable_type} todo as done" do
todos = TodosFinder.new(maintainer).execute
todo = todos.first
expect(todo.reload).to be_pending
expect(todos.size).to eq 1
expect(todo.target).to eq issuable
expect(todo.author).to eq maintainer
expect(todo.user).to eq maintainer
add_note('/done')
expect(page).not_to have_content 'Commands applied'
expect(todo.reload).to be_pending
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains done quick action' do
TodoService.new.mark_todo(issuable, maintainer)
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/done')
expect(page).not_to have_content '/done'
expect(page).to have_content "Marks todo as done."
end
end
end
# frozen_string_literal: true
shared_examples 'estimate quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets estimate quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/estimate 1d 2h 3m"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.time_estimate).to eq 36180
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the estimate quick action accordingly' do
add_note("/estimate 1d 2h 3m")
wait_for_requests
expect(page).not_to have_content '/estimate'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.time_estimate).to eq 36180
end
context "when current user cannot set estimate to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not set estimate' do
add_note("/estimate ~bug ~feature")
wait_for_requests
expect(page).not_to have_content '/estimate'
expect(issuable.reload.time_estimate).to eq 0
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains estimate quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/estimate 1d 2h 3m')
expect(page).not_to have_content '/estimate'
expect(page).to have_content 'Sets time estimate to 1d 2h 3m.'
end
end
end
# frozen_string_literal: true
shared_examples 'label quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets label quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug ~feature"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.labels).to match_array([label_bug, label_feature])
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
expect(issuable.labels).to eq []
end
it 'creates the note and interprets the label quick action accordingly' do
add_note("/label ~bug ~feature")
wait_for_requests
expect(page).not_to have_content '/label'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.labels).to match_array([label_bug, label_feature])
end
context "when current user cannot set label to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not set label' do
add_note("/label ~bug ~feature")
wait_for_requests
expect(page).not_to have_content '/label'
expect(issuable.labels).to eq []
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains label quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/label ~bug ~feature')
expect(page).not_to have_content '/label'
expect(page).to have_content 'Adds bug feature labels.'
end
end
end
# frozen_string_literal: true
shared_examples 'lock quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets lock quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/lock"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable).not_to be_discussion_locked
end
end
context "post note to existing #{issuable_type}" do
before do
issuable.update(discussion_locked: false)
expect(issuable).not_to be_discussion_locked
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the lock quick action accordingly' do
add_note('/lock')
wait_for_requests
expect(page).not_to have_content '/lock'
expect(page).to have_content 'Commands applied'
expect(issuable.reload).to be_discussion_locked
end
context "when current user cannot lock to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it "does not lock the #{issuable_type}" do
add_note('/lock')
wait_for_requests
expect(page).not_to have_content '/lock'
expect(issuable).not_to be_discussion_locked
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains lock quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/lock')
expect(page).not_to have_content '/lock'
expect(page).to have_content "Locks the discussion"
end
end
end
# frozen_string_literal: true
shared_examples 'milestone quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets milestone quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/milestone %\"ASAP\""
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.milestone).to eq milestone
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
expect(issuable.milestone).to be_nil
end
it 'creates the note and interprets the milestone quick action accordingly' do
add_note("/milestone %\"ASAP\"")
wait_for_requests
expect(page).not_to have_content '/milestone'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.milestone).to eq milestone
end
context "when current user cannot set milestone to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not set milestone' do
add_note('/milestone')
wait_for_requests
expect(page).not_to have_content '/milestone'
expect(issuable.reload.milestone).to be_nil
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains milestone quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note("/milestone %\"ASAP\"")
expect(page).not_to have_content '/milestone'
expect(page).to have_content 'Sets the milestone to %ASAP'
end
end
end
# frozen_string_literal: true
shared_examples 'relabel quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets relabel quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /relabel ~feature"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.labels).to eq [label_bug, label_feature]
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
issuable.update(labels: [label_bug])
end
it 'creates the note and interprets the relabel quick action accordingly' do
add_note('/relabel ~feature')
wait_for_requests
expect(page).not_to have_content '/relabel'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.labels).to match_array([label_feature])
end
it 'creates the note and interprets the relabel quick action with empty param' do
add_note('/relabel')
wait_for_requests
expect(page).not_to have_content '/relabel'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.labels).to match_array([label_bug])
end
context "when current user cannot relabel to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not relabel' do
add_note('/relabel ~feature')
wait_for_requests
expect(page).not_to have_content '/relabel'
expect(issuable.labels).to match_array([label_bug])
end
end
end
context "preview of note on #{issuable_type}", :js do
before do
issuable.update(labels: [label_bug])
visit public_send("project_#{issuable_type}_path", project, issuable)
end
it 'explains relabel all quick action' do
preview_note('/relabel ~feature')
expect(page).not_to have_content '/relabel'
expect(page).to have_content 'Replaces all labels with feature label.'
end
end
end
# frozen_string_literal: true
shared_examples 'remove_estimate quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets estimate quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/remove_estimate"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.time_estimate).to eq 0
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
issuable.update_attribute(:time_estimate, 36180)
end
it 'creates the note and interprets the remove_estimate quick action accordingly' do
add_note("/remove_estimate")
wait_for_requests
expect(page).not_to have_content '/remove_estimate'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.time_estimate).to eq 0
end
context "when current user cannot remove_estimate" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not remove_estimate' do
add_note('/remove_estimate')
wait_for_requests
expect(page).not_to have_content '/remove_estimate'
expect(issuable.reload.time_estimate).to eq 36180
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains remove_estimate quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/remove_estimate')
expect(page).not_to have_content '/remove_estimate'
expect(page).to have_content 'Removes time estimate.'
end
end
end
# frozen_string_literal: true
shared_examples 'remove_milestone quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets remove_milestone quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/remove_milestone"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.milestone).to be_nil
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
issuable.update(milestone: milestone)
expect(issuable.milestone).to eq(milestone)
end
it 'creates the note and interprets the remove_milestone quick action accordingly' do
add_note("/remove_milestone")
wait_for_requests
expect(page).not_to have_content '/remove_milestone'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.milestone).to be_nil
end
context "when current user cannot remove milestone to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not remove milestone' do
add_note('/remove_milestone')
wait_for_requests
expect(page).not_to have_content '/remove_milestone'
expect(issuable.reload.milestone).to eq(milestone)
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains remove_milestone quick action' do
issuable.update(milestone: milestone)
expect(issuable.milestone).to eq(milestone)
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note("/remove_milestone")
expect(page).not_to have_content '/remove_milestone'
expect(page).to have_content 'Removes %ASAP milestone.'
end
end
end
# frozen_string_literal: true
shared_examples 'remove_time_spent quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets remove_time_spent quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/remove_time_spent"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.total_time_spent).to eq 0
end
end
context "post note to existing #{issuable_type}" do
before do
issuable.update!(spend_time: { duration: 36180, user_id: maintainer.id })
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the remove_time_spent quick action accordingly' do
add_note("/remove_time_spent")
wait_for_requests
expect(page).not_to have_content '/remove_time_spent'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.total_time_spent).to eq 0
end
context "when current user cannot set remove_time_spent time" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not set remove_time_spent time' do
add_note("/remove_time_spent")
wait_for_requests
expect(page).not_to have_content '/remove_time_spent'
expect(issuable.reload.total_time_spent).to eq 36180
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains remove_time_spent quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/remove_time_spent')
expect(page).not_to have_content '/remove_time_spent'
expect(page).to have_content 'Removes spent time.'
end
end
end
# frozen_string_literal: true
shared_examples 'reopen quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets reopen quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/reopen"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
end
end
context "post note to existing #{issuable_type}" do
before do
issuable.close
expect(issuable).to be_closed
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the reopen quick action accordingly' do
add_note('/reopen')
wait_for_requests
expect(page).not_to have_content '/reopen'
expect(page).to have_content 'Commands applied'
issuable.reload
expect(issuable).to be_opened
end
context "when current user cannot reopen #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it "does not reopen the #{issuable_type}" do
add_note('/reopen')
expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_closed
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains reopen quick action' do
issuable.close
expect(issuable).to be_closed
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/reopen')
expect(page).not_to have_content '/reopen'
expect(page).to have_content "Reopens this #{issuable_type.to_s.humanize.downcase}."
end
end
end
# frozen_string_literal: true
shared_examples 'shrug quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets shrug quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/shrug oops"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq "bug description\noops ¯\\_(ツ)_/¯"
expect(page).to have_content 'bug 345'
expect(page).to have_content "bug description\noops ¯\\_(ツ)_/¯"
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets shrug quick action accordingly' do
add_note("/shrug oops")
wait_for_requests
expect(page).not_to have_content '/shrug oops'
expect(page).to have_content "oops ¯\\_(ツ)_/¯"
expect(issuable.notes.last.note).to eq "oops ¯\\_(ツ)_/¯"
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains shrug quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/shrug oops')
expect(page).not_to have_content '/shrug'
expect(page).to have_content "oops ¯\\_(ツ)_/¯"
end
end
end
# frozen_string_literal: true
shared_examples 'spend quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets spend quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/spend 1d 2h 3m"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.total_time_spent).to eq 36180
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the spend quick action accordingly' do
add_note("/spend 1d 2h 3m")
wait_for_requests
expect(page).not_to have_content '/spend'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.total_time_spent).to eq 36180
end
context "when current user cannot set spend time" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not set spend time' do
add_note("/spend 1s 2h 3m")
wait_for_requests
expect(page).not_to have_content '/spend'
expect(issuable.reload.total_time_spent).to eq 0
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains spend quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/spend 1d 2h 3m')
expect(page).not_to have_content '/spend'
expect(page).to have_content 'Adds 1d 2h 3m spent time.'
end
end
end
# frozen_string_literal: true
shared_examples 'subscribe quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets subscribe quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/subscribe"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.subscribed?(maintainer, project)).to be_truthy
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
expect(issuable.subscribed?(maintainer, project)).to be_falsy
end
it 'creates the note and interprets the subscribe quick action accordingly' do
add_note('/subscribe')
wait_for_requests
expect(page).not_to have_content '/subscribe'
expect(page).to have_content 'Commands applied'
expect(issuable.subscribed?(maintainer, project)).to be_truthy
end
context "when current user cannot subscribe to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it "does not subscribe to the #{issuable_type}" do
add_note('/subscribe')
wait_for_requests
expect(page).not_to have_content '/subscribe'
expect(page).to have_content 'Commands applied'
expect(issuable.subscribed?(maintainer, project)).to be_falsy
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains subscribe quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/subscribe')
expect(page).not_to have_content '/subscribe'
expect(page).to have_content "Subscribes to this #{issuable_type.to_s.humanize.downcase}"
end
end
end
# frozen_string_literal: true
shared_examples 'tableflip quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets tableflip quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/tableflip oops"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq "bug description\noops (╯°□°)╯︵ ┻━┻"
expect(page).to have_content 'bug 345'
expect(page).to have_content "bug description\noops (╯°□°)╯︵ ┻━┻"
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets tableflip quick action accordingly' do
add_note("/tableflip oops")
wait_for_requests
expect(page).not_to have_content '/tableflip oops'
expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻"
expect(issuable.notes.last.note).to eq "oops (╯°□°)╯︵ ┻━┻"
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains tableflip quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/tableflip oops')
expect(page).not_to have_content '/tableflip'
expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻"
end
end
end
# frozen_string_literal: true
shared_examples 'issuable time tracker' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
after do
wait_for_requests
end
it 'renders the sidebar component empty state' do
page.within '.time-tracking-no-tracking-pane' do
expect(page).to have_content 'No estimate or time spent'
end
end
it 'updates the sidebar component when estimate is added' do
submit_time('/estimate 3w 1d 1h')
wait_for_requests
page.within '.time-tracking-estimate-only-pane' do
expect(page).to have_content '3w 1d 1h'
end
end
it 'updates the sidebar component when spent is added' do
submit_time('/spend 3w 1d 1h')
wait_for_requests
page.within '.time-tracking-spend-only-pane' do
expect(page).to have_content '3w 1d 1h'
end
end
it 'shows the comparison when estimate and spent are added' do
submit_time('/estimate 3w 1d 1h')
submit_time('/spend 3w 1d 1h')
wait_for_requests
page.within '.time-tracking-comparison-pane' do
expect(page).to have_content '3w 1d 1h'
end
end
it 'updates the sidebar component when estimate is removed' do
submit_time('/estimate 3w 1d 1h')
submit_time('/remove_estimate')
page.within '.time-tracking-component-wrap' do
expect(page).to have_content 'No estimate or time spent'
end
end
it 'updates the sidebar component when spent is removed' do
submit_time('/spend 3w 1d 1h')
submit_time('/remove_time_spent')
page.within '.time-tracking-component-wrap' do
expect(page).to have_content 'No estimate or time spent'
end
end
it 'shows the help state when icon is clicked' do
page.within '.time-tracking-component-wrap' do
find('.help-button').click
expect(page).to have_content 'Track time with quick actions'
expect(page).to have_content 'Learn more'
end
end
it 'hides the help state when close icon is clicked' do
page.within '.time-tracking-component-wrap' do
find('.help-button').click
find('.close-help-button').click
expect(page).not_to have_content 'Track time with quick actions'
expect(page).not_to have_content 'Learn more'
end
end
it 'displays the correct help url' do
page.within '.time-tracking-component-wrap' do
find('.help-button').click
expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md')
end
end
end
def submit_time(quick_action)
fill_in 'note[note]', with: quick_action
find('.js-comment-submit-button').click
wait_for_requests
end
# frozen_string_literal: true
shared_examples 'title quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets title quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/title new title"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(issuable.title).to eq 'bug 345'
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the title quick action accordingly' do
add_note('/title New title')
wait_for_requests
expect(page).not_to have_content '/title new title'
expect(page).to have_content 'Commands applied'
expect(page).to have_content 'New title'
issuable.reload
expect(issuable.title).to eq 'New title'
end
context "when current user cannot set title #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it "does not set title to the #{issuable_type}" do
add_note('/title New title')
expect(page).not_to have_content 'Commands applied'
expect(issuable.title).not_to eq 'New title'
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains title quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/title New title')
wait_for_requests
expect(page).not_to have_content '/title New title'
expect(page).to have_content 'Changes the title to "New title".'
end
end
end
# frozen_string_literal: true
shared_examples 'todo quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets todo quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/todo"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
todos = TodosFinder.new(maintainer).execute
expect(todos.size).to eq 0
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the todo quick action accordingly' do
add_note('/todo')
wait_for_requests
expect(page).not_to have_content '/todo'
expect(page).to have_content 'Commands applied'
todos = TodosFinder.new(maintainer).execute
todo = todos.first
expect(todos.size).to eq 1
expect(todo).to be_pending
expect(todo.target).to eq issuable
expect(todo.author).to eq maintainer
expect(todo.user).to eq maintainer
end
context "when current user cannot add todo #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it "does not add todo the #{issuable_type}" do
add_note('/todo')
expect(page).not_to have_content 'Commands applied'
todos = TodosFinder.new(maintainer).execute
expect(todos.size).to eq 0
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains todo quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/todo')
expect(page).not_to have_content '/todo'
expect(page).to have_content "Adds a todo."
end
end
end
# frozen_string_literal: true
shared_examples 'unassign quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets unassign quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/unassign @bob"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable.assignees).to eq []
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
end
it "creates the #{issuable_type} and interprets unassign quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/unassign me"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable.assignees).to eq []
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the unassign quick action accordingly' do
assignee = create(:user, username: 'bob')
issuable.update(assignee_ids: [assignee.id])
expect(issuable.assignees).to eq [assignee]
add_note("Awesome!\n\n/unassign @bob")
expect(page).to have_content 'Awesome!'
expect(page).not_to have_content '/unassign @bob'
wait_for_requests
issuable.reload
note = issuable.notes.user.first
expect(note.note).to eq 'Awesome!'
expect(issuable.assignees).to eq []
end
it "unassigns the #{issuable_type} from current user" do
issuable.update(assignee_ids: [maintainer.id])
expect(issuable.reload.assignees).to eq [maintainer]
expect(issuable.assignees).to eq [maintainer]
add_note("/unassign me")
expect(page).not_to have_content '/unassign me'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.assignees).to eq []
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains unassign quick action: from bob' do
assignee = create(:user, username: 'bob')
issuable.update(assignee_ids: [assignee.id])
expect(issuable.assignees).to eq [assignee]
visit public_send("project_#{issuable_type}_path", project, issuable)
page.within('.js-main-target-form') do
fill_in 'note[note]', with: "Awesome!\n/unassign @bob "
click_on 'Preview'
expect(page).not_to have_content '/unassign @bob'
expect(page).to have_content 'Awesome!'
expect(page).to have_content 'Removes assignee @bob.'
end
end
it 'explains unassign quick action: from me' do
issuable.update(assignee_ids: [maintainer.id])
expect(issuable.assignees).to eq [maintainer]
visit public_send("project_#{issuable_type}_path", project, issuable)
page.within('.js-main-target-form') do
fill_in 'note[note]', with: "Awesome!\n/unassign me"
click_on 'Preview'
expect(page).not_to have_content '/unassign me'
expect(page).to have_content 'Awesome!'
expect(page).to have_content "Removes assignee @#{maintainer.username}."
end
end
end
end
# frozen_string_literal: true
shared_examples 'unlabel quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets unlabel quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /unlabel"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.labels).to eq [label_bug]
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
issuable.update(labels: [label_bug, label_feature])
end
it 'creates the note and interprets the unlabel all quick action accordingly' do
add_note("/unlabel")
wait_for_requests
expect(page).not_to have_content '/unlabel'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.labels).to eq []
end
it 'creates the note and interprets the unlabel some quick action accordingly' do
add_note("/unlabel ~bug")
wait_for_requests
expect(page).not_to have_content '/unlabel'
expect(page).to have_content 'Commands applied'
expect(issuable.reload.labels).to match_array([label_feature])
end
context "when current user cannot unlabel to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'does not unlabel' do
add_note("/unlabel")
wait_for_requests
expect(page).not_to have_content '/unlabel'
expect(issuable.labels).to match_array([label_bug, label_feature])
end
end
end
context "preview of note on #{issuable_type}", :js do
before do
issuable.update(labels: [label_bug, label_feature])
visit public_send("project_#{issuable_type}_path", project, issuable)
end
it 'explains unlabel all quick action' do
preview_note('/unlabel')
expect(page).not_to have_content '/unlabel'
expect(page).to have_content 'Removes all labels.'
end
it 'explains unlabel some quick action' do
preview_note('/unlabel ~bug')
expect(page).not_to have_content '/unlabel'
expect(page).to have_content 'Removes bug label.'
end
end
end
# frozen_string_literal: true
shared_examples 'unlock quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets unlock quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/unlock"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable).not_to be_discussion_locked
end
end
context "post note to existing #{issuable_type}" do
before do
issuable.update(discussion_locked: true)
expect(issuable).to be_discussion_locked
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it 'creates the note and interprets the unlock quick action accordingly' do
add_note('/unlock')
wait_for_requests
expect(page).not_to have_content '/unlock'
expect(page).to have_content 'Commands applied'
expect(issuable.reload).not_to be_discussion_locked
end
context "when current user cannot unlock to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it "does not lock the #{issuable_type}" do
add_note('/unlock')
wait_for_requests
expect(page).not_to have_content '/unlock'
expect(issuable).to be_discussion_locked
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains unlock quick action' do
issuable.update(discussion_locked: true)
expect(issuable).to be_discussion_locked
visit public_send("project_#{issuable_type}_path", project, issuable)
preview_note('/unlock')
expect(page).not_to have_content '/unlock'
expect(page).to have_content 'Unlocks the discussion'
end
end
end
# frozen_string_literal: true
shared_examples 'unsubscribe quick action' do |issuable_type|
before do
project.add_maintainer(maintainer)
gitlab_sign_in(maintainer)
end
context "new #{issuable_type}", :js do
before do
case issuable_type
when :merge_request
visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
wait_for_all_requests
when :issue
visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
wait_for_all_requests
end
end
it "creates the #{issuable_type} and interprets unsubscribe quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/unsubscribe"
click_button "Submit #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
expect(issuable.description).to eq 'bug description'
expect(issuable).to be_opened
expect(page).to have_content 'bug 345'
expect(page).to have_content 'bug description'
expect(issuable.subscribed?(maintainer, project)).to be_truthy
end
end
context "post note to existing #{issuable_type}" do
before do
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
issuable.subscribe(maintainer, project)
expect(issuable.subscribed?(maintainer, project)).to be_truthy
end
it 'creates the note and interprets the unsubscribe quick action accordingly' do
add_note('/unsubscribe')
wait_for_requests
expect(page).not_to have_content '/unsubscribe'
expect(page).to have_content 'Commands applied'
expect(issuable.subscribed?(maintainer, project)).to be_falsey
end
context "when current user cannot unsubscribe to #{issuable_type}" do
before do
guest = create(:user)
project.add_guest(guest)
gitlab_sign_out
gitlab_sign_in(guest)
visit public_send("project_#{issuable_type}_path", project, issuable)
wait_for_all_requests
end
it "does not unsubscribe to the #{issuable_type}" do
add_note('/unsubscribe')
wait_for_requests
expect(page).not_to have_content '/unsubscribe'
expect(issuable.subscribed?(maintainer, project)).to be_truthy
end
end
end
context "preview of note on #{issuable_type}", :js do
it 'explains unsubscribe quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
issuable.subscribe(maintainer, project)
expect(issuable.subscribed?(maintainer, project)).to be_truthy
preview_note('/unsubscribe')
expect(page).not_to have_content '/unsubscribe'
expect(page).to have_content "Unsubscribes from this #{issuable_type.to_s.humanize.downcase}."
end
end
end
# frozen_string_literal: true
shared_examples 'board_move quick action' do
end
# frozen_string_literal: true
shared_examples 'confidential quick action' do
end
# frozen_string_literal: true
shared_examples 'create_merge_request quick action' do
end
# frozen_string_literal: true
shared_examples 'due quick action not available' do
it 'does not set the due date' do
add_note('/due 2016-08-28')
expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content '/due 2016-08-28'
end
end
shared_examples 'due quick action available and date can be added' do
it 'sets the due date accordingly' do
add_note('/due 2016-08-28')
expect(page).not_to have_content '/due 2016-08-28'
expect(page).to have_content 'Commands applied'
visit project_issue_path(project, issue)
page.within '.due_date' do
expect(page).to have_content 'Aug 28, 2016'
end
end
end
# frozen_string_literal: true
shared_examples 'duplicate quick action' do
end
# frozen_string_literal: true
shared_examples 'move quick action' do
end
# frozen_string_literal: true
shared_examples 'remove_due_date action not available' do
it 'does not remove the due date' do
add_note("/remove_due_date")
expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content '/remove_due_date'
end
end
shared_examples 'remove_due_date action available and due date can be removed' do
it 'removes the due date accordingly' do
add_note('/remove_due_date')
expect(page).not_to have_content '/remove_due_date'
expect(page).to have_content 'Commands applied'
visit project_issue_path(project, issue)
page.within '.due_date' do
expect(page).to have_content 'No due date'
end
end
end
# frozen_string_literal: true
shared_examples 'merge quick action' do
end
# frozen_string_literal: true
shared_examples 'target_branch quick action' do
end
# frozen_string_literal: true
shared_examples 'wip quick action' do
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