New Notes::SlashCommandsService service
Check for update_issuable permission in Notes::SlashCommandsService Signed-off-by: Rémy Coutable <remy@rymai.me>
This commit is contained in:
parent
a54fdc384f
commit
7cc4ab14b8
10 changed files with 392 additions and 256 deletions
|
@ -81,21 +81,21 @@ class IssuableBaseService < BaseService
|
|||
end
|
||||
|
||||
def process_label_ids(attributes, base_label_ids: [], merge_all: false)
|
||||
label_ids = attributes.delete(:label_ids) { [] }
|
||||
add_label_ids = attributes.delete(:add_label_ids) { [] }
|
||||
remove_label_ids = attributes.delete(:remove_label_ids) { [] }
|
||||
label_ids = attributes.delete(:label_ids)
|
||||
add_label_ids = attributes.delete(:add_label_ids)
|
||||
remove_label_ids = attributes.delete(:remove_label_ids)
|
||||
|
||||
new_label_ids = base_label_ids
|
||||
new_label_ids |= label_ids if merge_all || (add_label_ids.empty? && remove_label_ids.empty?)
|
||||
new_label_ids |= add_label_ids
|
||||
new_label_ids -= remove_label_ids
|
||||
new_label_ids = label_ids if label_ids && (merge_all || (add_label_ids.empty? && remove_label_ids.empty?))
|
||||
new_label_ids |= add_label_ids if add_label_ids
|
||||
new_label_ids -= remove_label_ids if remove_label_ids
|
||||
|
||||
new_label_ids
|
||||
end
|
||||
|
||||
def merge_slash_commands_into_params!
|
||||
def merge_slash_commands_into_params!(issuable)
|
||||
command_params = SlashCommands::InterpretService.new(project, current_user).
|
||||
execute(params[:description])
|
||||
execute(params[:description], issuable)
|
||||
|
||||
params.merge!(command_params)
|
||||
end
|
||||
|
@ -115,7 +115,7 @@ class IssuableBaseService < BaseService
|
|||
end
|
||||
|
||||
def create(issuable)
|
||||
merge_slash_commands_into_params!
|
||||
merge_slash_commands_into_params!(issuable)
|
||||
filter_params
|
||||
|
||||
if params.present? && create_issuable(issuable, params)
|
||||
|
|
|
@ -14,7 +14,7 @@ module Notes
|
|||
# We execute commands (extracted from `params[:note]`) on the noteable
|
||||
# **before** we save the note because if the note consists of commands
|
||||
# only, there is no need be create a note!
|
||||
commands_executed = execute_slash_commands!(note)
|
||||
commands_executed = SlashCommandsService.new(project, current_user).execute(note)
|
||||
|
||||
if note.save
|
||||
# Finish the harder work in the background
|
||||
|
@ -22,50 +22,13 @@ module Notes
|
|||
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 commands_executed && note.note.blank?
|
||||
note.errors.add(:commands_only, 'Your commands are being executed.')
|
||||
end
|
||||
|
||||
note
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_slash_commands!(note)
|
||||
noteable_update_service = noteable_update_service(note.noteable_type)
|
||||
return unless noteable_update_service
|
||||
|
||||
command_params = SlashCommands::InterpretService.new(project, current_user).
|
||||
execute(note.note)
|
||||
|
||||
commands = execute_or_filter_commands(command_params, note)
|
||||
|
||||
if commands.any?
|
||||
noteable_update_service.new(project, current_user, commands).execute(note.noteable)
|
||||
end
|
||||
end
|
||||
|
||||
def execute_or_filter_commands(commands, note)
|
||||
final_commands = commands.reduce({}) do |memo, (command_key, command_value)|
|
||||
if command_key != :due_date || note.noteable.respond_to?(:due_date)
|
||||
memo[command_key] = command_value
|
||||
end
|
||||
|
||||
memo
|
||||
end
|
||||
|
||||
final_commands
|
||||
end
|
||||
|
||||
def noteable_update_service(noteable_type)
|
||||
case noteable_type
|
||||
when 'Issue'
|
||||
Issues::UpdateService
|
||||
when 'MergeRequest'
|
||||
MergeRequests::UpdateService
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
22
app/services/notes/slash_commands_service.rb
Normal file
22
app/services/notes/slash_commands_service.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
module Notes
|
||||
class SlashCommandsService < BaseService
|
||||
|
||||
UPDATE_SERVICES = {
|
||||
'Issue' => Issues::UpdateService,
|
||||
'MergeRequest' => MergeRequests::UpdateService
|
||||
}
|
||||
|
||||
def execute(note)
|
||||
noteable_update_service = UPDATE_SERVICES[note.noteable_type]
|
||||
return false unless noteable_update_service
|
||||
return false unless can?(current_user, :"update_#{note.noteable_type.underscore}", note.noteable)
|
||||
|
||||
commands = SlashCommands::InterpretService.new(project, current_user).
|
||||
execute(note.note, note.noteable)
|
||||
|
||||
if commands.any?
|
||||
noteable_update_service.new(project, current_user, commands).execute(note.noteable)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,9 +2,12 @@ module SlashCommands
|
|||
class InterpretService < BaseService
|
||||
include Gitlab::SlashCommands::Dsl
|
||||
|
||||
attr_reader :noteable
|
||||
|
||||
# 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)
|
||||
def execute(content, noteable)
|
||||
@noteable = noteable
|
||||
@updates = {}
|
||||
|
||||
commands = extractor.extract_commands!(content)
|
||||
|
@ -105,6 +108,8 @@ module SlashCommands
|
|||
desc 'Set a due date'
|
||||
params '<YYYY-MM-DD> | <N days>'
|
||||
command :due_date do |due_date_param|
|
||||
return unless noteable.respond_to?(:due_date)
|
||||
|
||||
due_date = begin
|
||||
Time.now + ChronicDuration.parse(due_date_param)
|
||||
rescue ChronicDuration::DurationParseError
|
||||
|
@ -116,6 +121,8 @@ module SlashCommands
|
|||
|
||||
desc 'Remove due date'
|
||||
command :clear_due_date do
|
||||
return unless noteable.respond_to?(:due_date)
|
||||
|
||||
@updates[:due_date] = nil
|
||||
end
|
||||
|
||||
|
|
|
@ -29,31 +29,4 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
|
|||
expect(page).not_to have_content '/due_date 2016-08-28'
|
||||
end
|
||||
end
|
||||
|
||||
# Postponed because of high complexity
|
||||
xdescribe 'merging from note' do
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
login_with(user)
|
||||
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
|
||||
end
|
||||
|
||||
it 'creates a note without the commands and interpret the commands accordingly' do
|
||||
page.within('.js-main-target-form') do
|
||||
fill_in 'note[note]', with: "Let's merge this!\n/merge\n/milestone %ASAP"
|
||||
click_button 'Comment'
|
||||
end
|
||||
|
||||
expect(page).to have_content("Let's merge this!")
|
||||
expect(page).not_to have_content('/merge')
|
||||
expect(page).not_to have_content('/milestone %ASAP')
|
||||
|
||||
merge_request.reload
|
||||
note = merge_request.notes.user.first
|
||||
|
||||
expect(note.note).to eq "Let's merge this!\r\n"
|
||||
expect(merge_request).to be_merged
|
||||
expect(merge_request.milestone).to eq milestone
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,25 +9,30 @@ describe Notes::CreateService, services: true do
|
|||
end
|
||||
|
||||
describe '#execute' do
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
end
|
||||
|
||||
context "valid params" do
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
@note = Notes::CreateService.new(project, user, opts).execute
|
||||
end
|
||||
|
||||
it { expect(@note).to be_valid }
|
||||
it { expect(@note.note).to eq(opts[:note]) }
|
||||
end
|
||||
|
||||
it_behaves_like 'note on noteable that supports slash commands' do
|
||||
let(:noteable) { create(:issue, project: project) }
|
||||
end
|
||||
describe 'note with commands' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) }
|
||||
|
||||
it_behaves_like 'note on noteable that supports slash commands' do
|
||||
let(:noteable) { create(:merge_request, source_project: project) }
|
||||
end
|
||||
it 'saves the note and does not alter the note text' do
|
||||
expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original
|
||||
|
||||
it_behaves_like 'note on noteable that does not support slash commands' do
|
||||
let(:noteable) { create(:commit, project: project) }
|
||||
note = described_class.new(project, user, opts.merge(note: note_text)).execute
|
||||
|
||||
expect(note.note).to eq "HELLO\nWORLD"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
129
spec/services/notes/slash_commands_service_spec.rb
Normal file
129
spec/services/notes/slash_commands_service_spec.rb
Normal file
|
@ -0,0 +1,129 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Notes::SlashCommandsService, services: true do
|
||||
shared_context 'note on noteable' do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:master) { create(:user).tap { |u| project.team << [u, :master] } }
|
||||
let(:assignee) { create(:user) }
|
||||
end
|
||||
|
||||
shared_examples 'note on noteable that does not support slash commands' do
|
||||
include_context 'note on noteable'
|
||||
|
||||
before do
|
||||
note.note = note_text
|
||||
described_class.new(project, master).execute(note)
|
||||
end
|
||||
|
||||
describe 'note with only command' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:note_text) { %(/close\n/assign @#{assignee.username}") }
|
||||
|
||||
it 'saves the note and does not alter the note text' do
|
||||
expect(note.note).to eq note_text
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'note with command & text' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:note_text) { %(HELLO\n/close\n/assign @#{assignee.username}\nWORLD) }
|
||||
|
||||
it 'saves the note and does not alter the note text' do
|
||||
expect(note.note).to eq note_text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'note on noteable that supports slash commands' do
|
||||
include_context 'note on noteable'
|
||||
|
||||
before do
|
||||
note.note = note_text
|
||||
end
|
||||
|
||||
let!(:milestone) { create(:milestone, project: project) }
|
||||
let!(:labels) { create_pair(:label, project: project) }
|
||||
|
||||
describe 'note with only command' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:note_text) do
|
||||
%(/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n/milestone %"#{milestone.name}")
|
||||
end
|
||||
|
||||
it 'closes noteable, sets labels, assigns, and sets milestone to noteable, and leave no note' do
|
||||
described_class.new(project, master).execute(note)
|
||||
|
||||
expect(note.note).to eq ''
|
||||
expect(note.noteable).to be_closed
|
||||
expect(note.noteable.labels).to match_array(labels)
|
||||
expect(note.noteable.assignee).to eq(assignee)
|
||||
expect(note.noteable.milestone).to eq(milestone)
|
||||
end
|
||||
end
|
||||
|
||||
describe '/open' do
|
||||
before do
|
||||
note.noteable.close!
|
||||
expect(note.noteable).to be_closed
|
||||
end
|
||||
let(:note_text) { '/open' }
|
||||
|
||||
it 'opens the noteable, and leave no note' do
|
||||
described_class.new(project, master).execute(note)
|
||||
|
||||
expect(note.note).to eq ''
|
||||
expect(note.noteable).to be_open
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'note with command & text' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:note_text) do
|
||||
%(HELLO\n/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n/milestone %"#{milestone.name}"\nWORLD)
|
||||
end
|
||||
|
||||
it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do
|
||||
described_class.new(project, master).execute(note)
|
||||
|
||||
expect(note.note).to eq "HELLO\nWORLD"
|
||||
expect(note.noteable).to be_closed
|
||||
expect(note.noteable.labels).to match_array(labels)
|
||||
expect(note.noteable.assignee).to eq(assignee)
|
||||
expect(note.noteable.milestone).to eq(milestone)
|
||||
end
|
||||
end
|
||||
|
||||
describe '/open' do
|
||||
before do
|
||||
note.noteable.close
|
||||
expect(note.noteable).to be_closed
|
||||
end
|
||||
let(:note_text) { "HELLO\n/open\nWORLD" }
|
||||
|
||||
it 'opens the noteable' do
|
||||
described_class.new(project, master).execute(note)
|
||||
|
||||
expect(note.note).to eq "HELLO\nWORLD"
|
||||
expect(note.noteable).to be_open
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
it_behaves_like 'note on noteable that supports slash commands' do
|
||||
let(:note) { build(:note_on_issue, project: project) }
|
||||
end
|
||||
|
||||
it_behaves_like 'note on noteable that supports slash commands' do
|
||||
let(:note) { build(:note_on_merge_request, project: project) }
|
||||
end
|
||||
|
||||
it_behaves_like 'note on noteable that does not support slash commands' do
|
||||
let(:note) { build(:note_on_commit, project: project) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,8 +18,7 @@ describe SlashCommands::InterpretService, services: true do
|
|||
:assign, :reassign,
|
||||
:unassign, :remove_assignee,
|
||||
:milestone,
|
||||
:remove_milestone,
|
||||
:clear_milestone,
|
||||
:clear_milestone, :remove_milestone,
|
||||
:labels, :label,
|
||||
:unlabel, :remove_labels, :remove_label,
|
||||
:clear_labels, :clear_label,
|
||||
|
@ -35,10 +34,12 @@ describe SlashCommands::InterpretService, services: true do
|
|||
|
||||
describe '#execute' do
|
||||
let(:service) { described_class.new(project, user) }
|
||||
let(:issue) { create(:issue) }
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
|
||||
shared_examples 'open command' do
|
||||
it 'returns state_event: "open" if content contains /open' do
|
||||
changes = service.execute(content)
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(state_event: 'reopen')
|
||||
end
|
||||
|
@ -46,7 +47,7 @@ describe SlashCommands::InterpretService, services: true do
|
|||
|
||||
shared_examples 'close command' do
|
||||
it 'returns state_event: "close" if content contains /open' do
|
||||
changes = service.execute(content)
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(state_event: 'close')
|
||||
end
|
||||
|
@ -54,31 +55,47 @@ describe SlashCommands::InterpretService, services: true do
|
|||
|
||||
shared_examples 'assign command' do
|
||||
it 'fetches assignee and populates assignee_id if content contains /assign' do
|
||||
changes = service.execute(content)
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(assignee_id: user.id)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'unassign command' do
|
||||
it 'populates assignee_id: nil if content contains /unassign' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(assignee_id: nil)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'milestone command' do
|
||||
it 'fetches milestone and populates milestone_id if content contains /milestone' do
|
||||
changes = service.execute(content)
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(milestone_id: milestone.id)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'clear_milestone command' do
|
||||
it 'populates milestone_id: nil if content contains /clear_milestone' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(milestone_id: nil)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'label command' do
|
||||
it 'fetches label ids and populates add_label_ids if content contains /label' do
|
||||
changes = service.execute(content)
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(add_label_ids: [bug.id, inprogress.id])
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'remove_labels command' do
|
||||
it 'fetches label ids and populates remove_label_ids if content contains /label' do
|
||||
changes = service.execute(content)
|
||||
shared_examples 'unlabel command' do
|
||||
it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(remove_label_ids: [inprogress.id])
|
||||
end
|
||||
|
@ -86,15 +103,63 @@ describe SlashCommands::InterpretService, services: true do
|
|||
|
||||
shared_examples 'clear_labels command' do
|
||||
it 'populates label_ids: [] if content contains /clear_labels' do
|
||||
changes = service.execute(content)
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(label_ids: [])
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'command returning no changes' do
|
||||
it 'returns an empty hash if content contains /open' do
|
||||
changes = service.execute(content)
|
||||
shared_examples 'todo command' do
|
||||
it 'populates todo_event: "mark" if content contains /todo' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(todo_event: 'mark')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'done command' do
|
||||
it 'populates todo_event: "done" if content contains /done' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(todo_event: 'done')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'subscribe command' do
|
||||
it 'populates subscription_event: "subscribe" if content contains /subscribe' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(subscription_event: 'subscribe')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'unsubscribe command' do
|
||||
it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(subscription_event: 'unsubscribe')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'due_date command' do
|
||||
it 'populates due_date: Date.new(2016, 8, 28) if content contains /due_date 2016-08-28' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(due_date: Date.new(2016, 8, 28))
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'clear_due_date command' do
|
||||
it 'populates due_date: nil if content contains /clear_due_date' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to eq(due_date: nil)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'empty command' do
|
||||
it 'populates {} if content contains an unsupported command' do
|
||||
changes = service.execute(content, issuable)
|
||||
|
||||
expect(changes).to be_empty
|
||||
end
|
||||
|
@ -102,116 +167,204 @@ describe SlashCommands::InterpretService, services: true do
|
|||
|
||||
it_behaves_like 'open command' do
|
||||
let(:content) { '/open' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'open command' do
|
||||
let(:content) { '/open' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'open command' do
|
||||
let(:content) { '/reopen' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'close command' do
|
||||
let(:content) { '/close' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'close command' do
|
||||
let(:content) { '/close' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'assign command' do
|
||||
let(:content) { "/assign @#{user.username}" }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'assign command' do
|
||||
let(:content) { "/assign @#{user.username}" }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it 'does not populate assignee_id if content contains /assign with an unknown user' do
|
||||
changes = service.execute('/assign joe')
|
||||
changes = service.execute('/assign joe', issue)
|
||||
|
||||
expect(changes).to be_empty
|
||||
end
|
||||
|
||||
it 'does not populate assignee_id if content contains /assign without user' do
|
||||
changes = service.execute('/assign')
|
||||
changes = service.execute('/assign', issue)
|
||||
|
||||
expect(changes).to be_empty
|
||||
end
|
||||
|
||||
it 'populates assignee_id: nil if content contains /unassign' do
|
||||
changes = service.execute('/unassign')
|
||||
it_behaves_like 'unassign command' do
|
||||
let(:content) { '/unassign' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
expect(changes).to eq(assignee_id: nil)
|
||||
it_behaves_like 'unassign command' do
|
||||
let(:content) { '/unassign' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'unassign command' do
|
||||
let(:content) { '/remove_assignee' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'milestone command' do
|
||||
let(:content) { "/milestone %#{milestone.title}" }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it 'populates milestone_id: nil if content contains /clear_milestone' do
|
||||
changes = service.execute('/clear_milestone')
|
||||
it_behaves_like 'milestone command' do
|
||||
let(:content) { "/milestone %#{milestone.title}" }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
expect(changes).to eq(milestone_id: nil)
|
||||
it_behaves_like 'clear_milestone command' do
|
||||
let(:content) { '/clear_milestone' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'clear_milestone command' do
|
||||
let(:content) { '/clear_milestone' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'clear_milestone command' do
|
||||
let(:content) { '/remove_milestone' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'label command' do
|
||||
let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'label command' do
|
||||
let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'label command' do
|
||||
let(:content) { %(/labels ~"#{inprogress.title}" ~#{bug.title} ~unknown) }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'remove_labels command' do
|
||||
it_behaves_like 'unlabel command' do
|
||||
let(:content) { %(/unlabel ~"#{inprogress.title}") }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'remove_labels command' do
|
||||
it_behaves_like 'unlabel command' do
|
||||
let(:content) { %(/unlabel ~"#{inprogress.title}") }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'unlabel command' do
|
||||
let(:content) { %(/remove_labels ~"#{inprogress.title}") }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'remove_labels command' do
|
||||
it_behaves_like 'unlabel command' do
|
||||
let(:content) { %(/remove_label ~"#{inprogress.title}") }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'clear_labels command' do
|
||||
let(:content) { '/clear_labels' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'clear_labels command' do
|
||||
let(:content) { '/clear_labels' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'clear_labels command' do
|
||||
let(:content) { '/clear_label' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it 'populates todo: :mark if content contains /todo' do
|
||||
changes = service.execute('/todo')
|
||||
|
||||
expect(changes).to eq(todo_event: 'mark')
|
||||
it_behaves_like 'todo command' do
|
||||
let(:content) { '/todo' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it 'populates todo: :done if content contains /done' do
|
||||
changes = service.execute('/done')
|
||||
|
||||
expect(changes).to eq(todo_event: 'done')
|
||||
it_behaves_like 'todo command' do
|
||||
let(:content) { '/todo' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it 'populates subscription: :subscribe if content contains /subscribe' do
|
||||
changes = service.execute('/subscribe')
|
||||
|
||||
expect(changes).to eq(subscription_event: 'subscribe')
|
||||
it_behaves_like 'done command' do
|
||||
let(:content) { '/done' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it 'populates subscription: :unsubscribe if content contains /unsubscribe' do
|
||||
changes = service.execute('/unsubscribe')
|
||||
|
||||
expect(changes).to eq(subscription_event: 'unsubscribe')
|
||||
it_behaves_like 'done command' do
|
||||
let(:content) { '/done' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it 'populates due_date: Time.now.tomorrow if content contains /due_date 2016-08-28' do
|
||||
changes = service.execute('/due_date 2016-08-28')
|
||||
|
||||
expect(changes).to eq(due_date: Date.new(2016, 8, 28))
|
||||
it_behaves_like 'subscribe command' do
|
||||
let(:content) { '/subscribe' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it 'populates due_date: Time.now.tomorrow if content contains /due_date foo' do
|
||||
changes = service.execute('/due_date foo')
|
||||
|
||||
expect(changes).to be_empty
|
||||
it_behaves_like 'subscribe command' do
|
||||
let(:content) { '/subscribe' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it 'populates due_date: nil if content contains /clear_due_date' do
|
||||
changes = service.execute('/clear_due_date')
|
||||
it_behaves_like 'unsubscribe command' do
|
||||
let(:content) { '/unsubscribe' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
expect(changes).to eq(due_date: nil)
|
||||
it_behaves_like 'unsubscribe command' do
|
||||
let(:content) { '/unsubscribe' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'due_date command' do
|
||||
let(:content) { '/due_date 2016-08-28' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'empty command' do
|
||||
let(:content) { '/due_date foo bar' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'empty command' do
|
||||
let(:content) { '/due_date 2016-08-28' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
||||
it_behaves_like 'clear_due_date command' do
|
||||
let(:content) { '/clear_due_date' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'empty command' do
|
||||
let(:content) { '/clear_due_date' }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -118,7 +118,7 @@ shared_examples 'issuable record that supports slash commands in its description
|
|||
end
|
||||
|
||||
expect(page).not_to have_content '/close'
|
||||
expect(page).to have_content 'Your commands are being executed.'
|
||||
expect(page).not_to have_content 'Your commands are being executed.'
|
||||
|
||||
expect(issuable).to be_open
|
||||
end
|
||||
|
@ -159,7 +159,7 @@ shared_examples 'issuable record that supports slash commands in its description
|
|||
end
|
||||
|
||||
expect(page).not_to have_content '/reopen'
|
||||
expect(page).to have_content 'Your commands are being executed.'
|
||||
expect(page).not_to have_content 'Your commands are being executed.'
|
||||
|
||||
expect(issuable).to be_closed
|
||||
end
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
# Specifications for behavior common to all note objects with executable attributes.
|
||||
# It expects a `noteable` object for which the note is posted.
|
||||
|
||||
shared_context 'note on noteable' do
|
||||
let!(:project) { create(:project) }
|
||||
let(:user) { create(:user).tap { |u| project.team << [u, :master] } }
|
||||
let(:assignee) { create(:user) }
|
||||
let(:base_params) { { noteable: noteable } }
|
||||
let(:params) { base_params.merge(example_params) }
|
||||
let(:note) { described_class.new(project, user, params).execute }
|
||||
end
|
||||
|
||||
shared_examples 'note on noteable that does not support slash commands' do
|
||||
include_context 'note on noteable'
|
||||
|
||||
let(:params) { { commit_id: noteable.id, noteable_type: 'Commit' }.merge(example_params) }
|
||||
|
||||
describe 'note with only command' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:note_text) { %(/close\n/assign @#{assignee.username}") }
|
||||
let(:example_params) { { note: note_text } }
|
||||
|
||||
it 'saves the note and does not alter the note text' do
|
||||
expect(note).to be_persisted
|
||||
expect(note.note).to eq note_text
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'note with command & text' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:note_text) { %(HELLO\n/close\n/assign @#{assignee.username}\nWORLD) }
|
||||
let(:example_params) { { note: note_text } }
|
||||
|
||||
it 'saves the note and does not alter the note text' do
|
||||
expect(note).to be_persisted
|
||||
expect(note.note).to eq note_text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'note on noteable that supports slash commands' do
|
||||
include_context 'note on noteable'
|
||||
|
||||
let!(:milestone) { create(:milestone, project: project) }
|
||||
let!(:labels) { create_pair(:label, project: project) }
|
||||
|
||||
describe 'note with only command' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:example_params) do
|
||||
{
|
||||
note: %(/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n/milestone %"#{milestone.name}")
|
||||
}
|
||||
end
|
||||
|
||||
it 'closes noteable, sets labels, assigns, and sets milestone to noteable, and leave no note' do
|
||||
expect(note).not_to be_persisted
|
||||
expect(note.note).to eq ''
|
||||
expect(noteable).to be_closed
|
||||
expect(noteable.labels).to match_array(labels)
|
||||
expect(noteable.assignee).to eq(assignee)
|
||||
expect(noteable.milestone).to eq(milestone)
|
||||
end
|
||||
end
|
||||
|
||||
describe '/open' do
|
||||
let(:noteable) { create(:issue, project: project, state: :closed) }
|
||||
let(:example_params) do
|
||||
{
|
||||
note: '/open'
|
||||
}
|
||||
end
|
||||
|
||||
it 'opens the noteable, and leave no note' do
|
||||
expect(note).not_to be_persisted
|
||||
expect(note.note).to eq ''
|
||||
expect(noteable).to be_open
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'note with command & text' do
|
||||
describe '/close, /label, /assign & /milestone' do
|
||||
let(:example_params) do
|
||||
{
|
||||
note: %(HELLO\n/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n/milestone %"#{milestone.name}"\nWORLD)
|
||||
}
|
||||
end
|
||||
|
||||
it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do
|
||||
expect(note).to be_persisted
|
||||
expect(note.note).to eq "HELLO\nWORLD"
|
||||
expect(noteable).to be_closed
|
||||
expect(noteable.labels).to match_array(labels)
|
||||
expect(noteable.assignee).to eq(assignee)
|
||||
expect(noteable.milestone).to eq(milestone)
|
||||
end
|
||||
end
|
||||
|
||||
describe '/open' do
|
||||
let(:noteable) { create(:issue, project: project, state: :closed) }
|
||||
let(:example_params) do
|
||||
{
|
||||
note: "HELLO\n/open\nWORLD"
|
||||
}
|
||||
end
|
||||
|
||||
it 'opens the noteable' do
|
||||
expect(note).to be_persisted
|
||||
expect(note.note).to eq "HELLO\nWORLD"
|
||||
expect(noteable).to be_open
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue