GitLab wurde erfolgreich aktualisiert. Durch regelmäßige Updates bleibt das THM GitLab sicher. Danke für Ihre Geduld.

Commit 029b7d2e authored by Douwe Maan's avatar Douwe Maan

Fixed specs and fixes based on failing specs

parent b2b1b4a4
......@@ -176,12 +176,7 @@ def bulk_update
protected
def issue
@noteable = @issue ||=
begin
@project.issues.find_by!(iid: params[:id])
rescue ActiveRecord::RecordNotFound
redirect_old
end
@noteable = @issue ||= @project.issues.find_by(iid: params[:id]) || redirect_old
end
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
......@@ -225,7 +220,6 @@ def redirect_old
if issue
redirect_to issue_path(issue)
return
else
raise ActiveRecord::RecordNotFound.new
end
......
......@@ -15,20 +15,29 @@ def execute
# **before** we save the note because if the note consists of commands
# only, there is no need be create a note!
slash_commands_service = SlashCommandsService.new(project, current_user)
content, command_params = slash_commands_service.extract_commands(note)
note.note = content
if slash_commands_service.supported?(note)
content, command_params = slash_commands_service.extract_commands(note)
if note.save
only_commands = content.empty?
note.note = content
end
if !only_commands && note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
todo_service.new_note(note, current_user)
end
# We must add the error after we call #save because errors are reset
# when #save is called
if slash_commands_service.execute(command_params, note) && note.note.blank?
note.errors.add(:commands_only, 'Your commands have been executed!')
if command_params && command_params.any?
slash_commands_service.execute(command_params, note)
# We must add the error after we call #save because errors are reset
# when #save is called
if only_commands
note.errors.add(:commands_only, 'Your commands have been executed!')
end
end
note
......
......@@ -5,10 +5,13 @@ class SlashCommandsService < BaseService
'MergeRequest' => MergeRequests::UpdateService
}
def supported?(note)
noteable_update_service(note) &&
can?(current_user, :"update_#{note.noteable_type.underscore}", note.noteable)
end
def extract_commands(note)
@noteable_update_service = UPDATE_SERVICES[note.noteable_type]
return [] unless @noteable_update_service
return [] unless can?(current_user, :"update_#{note.noteable_type.underscore}", note.noteable)
return [note.note, {}] unless supported?(note)
SlashCommands::InterpretService.new(project, current_user).
execute(note.note, note.noteable)
......@@ -16,9 +19,15 @@ def extract_commands(note)
def execute(command_params, note)
return if command_params.empty?
return unless supported?(note)
noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable)
end
private
@noteable_update_service.new(project, current_user, command_params).
execute(note.noteable)
def noteable_update_service(note)
UPDATE_SERVICES[note.noteable_type]
end
end
end
......@@ -2,16 +2,16 @@ module SlashCommands
class InterpretService < BaseService
include Gitlab::SlashCommands::Dsl
attr_reader :noteable
attr_reader :issuable
# Takes a text and interpret the commands that are extracted from it.
# Returns a hash of changes to be applied to a record.
def execute(content, noteable)
@noteable = noteable
def execute(content, issuable)
@issuable = issuable
@updates = {}
opts = {
noteable: noteable,
issuable: issuable,
current_user: current_user,
project: project
}
......@@ -35,23 +35,24 @@ def extractor
end
desc do
"Close this #{noteable.to_ability_name.humanize(capitalize: false)}"
"Close this #{issuable.to_ability_name.humanize(capitalize: false)}"
end
condition do
noteable.persisted? &&
noteable.open? &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable)
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 #{noteable.to_ability_name.humanize(capitalize: false)}"
"Reopen this #{issuable.to_ability_name.humanize(capitalize: false)}"
end
condition do
noteable.closed? &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable)
issuable.persisted? &&
issuable.closed? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :reopen, :open do
@updates[:state_event] = 'reopen'
......@@ -60,8 +61,8 @@ def extractor
desc 'Change title'
params '<New title>'
condition do
noteable.persisted? &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable)
issuable.persisted? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :title do |title_param|
@updates[:title] = title_param
......@@ -70,7 +71,7 @@ def extractor
desc 'Assign'
params '@user'
condition do
current_user.can?(:"admin_#{noteable.to_ability_name}", project)
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :assign do |assignee_param|
user = extract_references(assignee_param, :user).first
......@@ -82,8 +83,9 @@ def extractor
desc 'Remove assignee'
condition do
noteable.assignee_id? &&
current_user.can?(:"admin_#{noteable.to_ability_name}", project)
issuable.persisted? &&
issuable.assignee_id? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :unassign, :remove_assignee do
@updates[:assignee_id] = nil
......@@ -92,7 +94,7 @@ def extractor
desc 'Set milestone'
params '%"milestone"'
condition do
current_user.can?(:"admin_#{noteable.to_ability_name}", project) &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
project.milestones.active.any?
end
command :milestone do |milestone_param|
......@@ -104,8 +106,9 @@ def extractor
desc 'Remove milestone'
condition do
noteable.milestone_id? &&
current_user.can?(:"admin_#{noteable.to_ability_name}", project)
issuable.persisted? &&
issuable.milestone_id? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :clear_milestone, :remove_milestone do
@updates[:milestone_id] = nil
......@@ -114,7 +117,7 @@ def extractor
desc 'Add label(s)'
params '~label1 ~"label 2"'
condition do
current_user.can?(:"admin_#{noteable.to_ability_name}", project) &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
project.labels.any?
end
command :label, :labels do |labels_param|
......@@ -126,8 +129,9 @@ def extractor
desc 'Remove label(s)'
params '~label1 ~"label 2"'
condition do
noteable.labels.any? &&
current_user.can?(:"admin_#{noteable.to_ability_name}", project)
issuable.persisted? &&
issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :unlabel, :remove_label, :remove_labels do |labels_param|
label_ids = find_label_ids(labels_param)
......@@ -137,8 +141,9 @@ def extractor
desc 'Remove all labels'
condition do
noteable.labels.any? &&
current_user.can?(:"admin_#{noteable.to_ability_name}", project)
issuable.persisted? &&
issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :clear_labels, :clear_label do
@updates[:label_ids] = []
......@@ -146,8 +151,8 @@ def extractor
desc 'Add a todo'
condition do
noteable.persisted? &&
!TodoService.new.todo_exist?(noteable, current_user)
issuable.persisted? &&
!TodoService.new.todo_exist?(issuable, current_user)
end
command :todo do
@updates[:todo_event] = 'add'
......@@ -155,7 +160,8 @@ def extractor
desc 'Mark todo as done'
condition do
TodoService.new.todo_exist?(noteable, current_user)
issuable.persisted? &&
TodoService.new.todo_exist?(issuable, current_user)
end
command :done do
@updates[:todo_event] = 'done'
......@@ -163,8 +169,8 @@ def extractor
desc 'Subscribe'
condition do
noteable.persisted? &&
!noteable.subscribed?(current_user)
issuable.persisted? &&
!issuable.subscribed?(current_user)
end
command :subscribe do
@updates[:subscription_event] = 'subscribe'
......@@ -172,8 +178,8 @@ def extractor
desc 'Unsubscribe'
condition do
noteable.persisted? &&
noteable.subscribed?(current_user)
issuable.persisted? &&
issuable.subscribed?(current_user)
end
command :unsubscribe do
@updates[:subscription_event] = 'unsubscribe'
......@@ -182,8 +188,8 @@ def extractor
desc 'Set due date'
params '<in 2 days; this Friday; December 31st>'
condition do
noteable.respond_to?(:due_date) &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable)
issuable.respond_to?(:due_date) &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :due, :due_date do |due_date_param|
due_date = Chronic.parse(due_date_param).try(:to_date)
......@@ -193,9 +199,10 @@ def extractor
desc 'Remove due date'
condition do
noteable.respond_to?(:due_date) &&
noteable.due_date? &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable)
issuable.persisted? &&
issuable.respond_to?(:due_date) &&
issuable.due_date? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :clear_due_date do
@updates[:due_date] = nil
......
......@@ -3,8 +3,8 @@ module SlashCommands
class CommandDefinition
attr_accessor :name, :aliases, :description, :params, :condition_block, :action_block
def valid?
name.present?
def initialize(name)
@name = name
end
def all_names
......@@ -22,13 +22,6 @@ def available?(opts)
context.instance_exec(&condition_block)
end
def to_description(opts)
return description unless description.respond_to?(:call)
context = OpenStruct.new(opts)
context.instance_exec(&description) rescue ''
end
def execute(context, opts, *args)
return if noop? || !available?(opts)
......
......@@ -73,16 +73,13 @@ def condition(&block)
def command(*command_names, &block)
name, *aliases = command_names
definition = CommandDefinition.new
definition.name = name
definition = CommandDefinition.new(name)
definition.aliases = aliases
definition.description = @description || ''
definition.params = @params || []
definition.condition_block = @condition_block
definition.action_block = block
return unless definition.valid?
self.command_definitions << definition
definition.all_names.each do |name|
......
......@@ -29,8 +29,8 @@ def initialize(command_definitions)
# commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']]
# msg #=> "hello\nworld"
# ```
def extract_commands(content, opts)
return [] unless content
def extract_commands(content, opts = {})
return [content, []] unless content
content = content.dup
......@@ -107,7 +107,13 @@ def commands_regex(opts)
# Command not in a blockquote, blockcode, or HTML tag:
# /close
^\/(?<cmd>#{Regexp.union(names)})(?:$|\ (?<args>[^\/\n]*)$)
^\/
(?<cmd>#{Regexp.union(names)})
(?:
[ ]
(?<args>[^\/\n]*)
)?
(?:\n|$)
)
}mx
end
......
require 'spec_helper'
describe Gitlab::SlashCommands::CommandDefinition do
subject { described_class.new(:command) }
describe "#all_names" do
context "when the command has aliases" do
before do
subject.aliases = [:alias1, :alias2]
end
it "returns an array with the name and aliases" do
expect(subject.all_names).to eq([:command, :alias1, :alias2])
end
end
context "when the command doesn't have aliases" do
it "returns an array with the name" do
expect(subject.all_names).to eq([:command])
end
end
end
describe "#noop?" do
context "when the command has an action block" do
before do
subject.action_block = -> { }
end
it "returns false" do
expect(subject.noop?).to be false
end
end
context "when the command doesn't have an action block" do
it "returns true" do
expect(subject.noop?).to be true
end
end
end
describe "#available?" do
let(:opts) { { go: false } }
context "when the command has a condition block" do
before do
subject.condition_block = -> { go }
end
context "when the condition block returns true" do
before do
opts[:go] = true
end
it "returns true" do
expect(subject.available?(opts)).to be true
end
end
context "when the condition block returns false" do
it "returns false" do
expect(subject.available?(opts)).to be false
end
end
end
context "when the command doesn't have a condition block" do
it "returns true" do
expect(subject.available?(opts)).to be true
end
end
end
describe "#execute" do
let(:context) { OpenStruct.new(run: false) }
context "when the command is a noop" do
it "doesn't execute the command" do
expect(context).not_to receive(:instance_exec)
subject.execute(context, {})
expect(context.run).to be false
end
end
context "when the command is not a noop" do
before do
subject.action_block = -> { self.run = true }
end
context "when the command is not available" do
before do
subject.condition_block = -> { false }
end
it "doesn't execute the command" do
subject.execute(context, {})
expect(context.run).to be false
end
end
context "when the command is available" do
context "when the command has an exact number of arguments" do
before do
subject.action_block = ->(arg) { self.run = arg }
end
context "when the command is provided a wrong number of arguments" do
it "doesn't execute the command" do
subject.execute(context, {}, true, true)
expect(context.run).to be false
end
end
context "when the command is provided the right number of arguments" do
it "executes the command" do
subject.execute(context, {}, true)
expect(context.run).to be true
end
end
end
context "when the command has a variable number of arguments" do
before do
subject.action_block = ->(*args) { self.run = args.first }
end
context "when the command is provided any number of arguments" do
it "executes the command" do
subject.execute(context, {}, true, true)
expect(context.run).to be true
end
end
end
end
end
end
end
......@@ -10,9 +10,9 @@
"Hello World!"
end
desc 'A command returning a value'
desc { "A command with #{something}" }
command :returning do
return 42
42
end
params 'The first argument'
......@@ -28,7 +28,7 @@
[arg1, arg2]
end
command :cc, noop: true
command :cc
condition do
project == 'foo'
......@@ -49,182 +49,74 @@
{
name: :no_args, aliases: [:none],
description: 'A command with no args', params: [],
condition_block: nil, action_block: a_kind_of(Proc),
opts: {}
condition_block: nil, action_block: a_kind_of(Proc)
},
{
name: :returning, aliases: [],
description: 'A command returning a value', params: [],
condition_block: nil, action_block: a_kind_of(Proc),
opts: {}
condition_block: nil, action_block: a_kind_of(Proc)
},
{
name: :one_arg, aliases: [:once, :first],
description: '', params: ['The first argument'],
condition_block: nil, action_block: a_kind_of(Proc),
opts: {}
condition_block: nil, action_block: a_kind_of(Proc)
},
{
name: :two_args, aliases: [],
description: '', params: ['The first argument', 'The second argument'],
condition_block: nil, action_block: a_kind_of(Proc),
opts: {}
condition_block: nil, action_block: a_kind_of(Proc)
},
{
name: :cc, aliases: [],
description: '', params: [],
condition_block: nil, action_block: nil,
opts: { noop: true }
condition_block: nil, action_block: nil
},
{
name: :wildcard, aliases: [],
description: '', params: [],
condition_block: nil, action_block: a_kind_of(Proc),
opts: {}
condition_block: nil, action_block: a_kind_of(Proc)
}
]
end
it 'returns an array with commands definitions' do
expect(DummyClass.command_definitions).to match_array base_expected
end
context 'with options passed' do
context 'when condition is met' do
let(:expected) do
base_expected << {
name: :cond_action, aliases: [],
description: '', params: [],
condition_block: a_kind_of(Proc), action_block: a_kind_of(Proc),
opts: {}
}
end
it 'returns an array with commands definitions' do
expect(DummyClass.command_definitions(project: 'foo')).to match_array expected
end
end
context 'when condition is not met' do
it 'returns an array with commands definitions without actions that did not met conditions' do
expect(DummyClass.command_definitions(project: 'bar')).to match_array base_expected
end
end
context 'when description can be generated dynamically' do
it 'returns an array with commands definitions with dynamic descriptions' do
base_expected[3][:description] = 'A dynamic description for MERGE REQUEST'
expect(DummyClass.command_definitions(noteable: 'merge request')).to match_array base_expected
end
end
end
end
describe '.command_names' do
let(:base_expected) do
[
:no_args, :none, :returning, :one_arg,
:once, :first, :two_args, :wildcard
]
end
it 'returns an array with commands definitions' do
expect(DummyClass.command_names).to eq base_expected
end
context 'with options passed' do
context 'when condition is met' do
let(:expected) { base_expected << :cond_action }
it 'returns an array with commands definitions' do
expect(DummyClass.command_names(project: 'foo')).to match_array expected