diff options
-rw-r--r-- | app/services/quick_actions/interpret_service.rb | 16 | ||||
-rw-r--r-- | changelogs/unreleased/29385-add_shrug_command.yml | 4 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/dsl.rb | 29 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/extractor.rb | 22 | ||||
-rw-r--r-- | lib/gitlab/quick_actions/substitution_definition.rb | 24 | ||||
-rw-r--r-- | spec/lib/gitlab/quick_actions/dsl_spec.rb | 16 | ||||
-rw-r--r-- | spec/lib/gitlab/quick_actions/extractor_spec.rb | 37 | ||||
-rw-r--r-- | spec/lib/gitlab/quick_actions/substitution_definition_spec.rb | 42 | ||||
-rw-r--r-- | spec/services/quick_actions/interpret_service_spec.rb | 42 |
9 files changed, 225 insertions, 7 deletions
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 5dc1b91d2c0..c22bf7498bb 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -4,6 +4,9 @@ module QuickActions attr_reader :issuable + SHRUG = '¯\\_(ツ)_/¯'.freeze + TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze + # 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. def execute(content, issuable) @@ -14,6 +17,7 @@ module QuickActions content, commands = extractor.extract_commands(content, context) extract_updates(commands, context) + [content, @updates] end @@ -423,6 +427,18 @@ module QuickActions @updates[:spend_time] = { duration: :reset, user: current_user } 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 + # This is a dummy command, so that it appears in the autocomplete commands desc 'CC' params '@user' diff --git a/changelogs/unreleased/29385-add_shrug_command.yml b/changelogs/unreleased/29385-add_shrug_command.yml new file mode 100644 index 00000000000..14b8f486d5c --- /dev/null +++ b/changelogs/unreleased/29385-add_shrug_command.yml @@ -0,0 +1,4 @@ +--- +title: Add /shrug and /tableflip commands +merge_request: 10068 +author: Alex Ives diff --git a/lib/gitlab/quick_actions/dsl.rb b/lib/gitlab/quick_actions/dsl.rb index a4a97236ffc..536765305e1 100644 --- a/lib/gitlab/quick_actions/dsl.rb +++ b/lib/gitlab/quick_actions/dsl.rb @@ -105,9 +105,32 @@ module Gitlab # # Awesome code block # end def command(*command_names, &block) + define_command(CommandDefinition, *command_names, &block) + end + + # Registers a new substitution which is recognizable from body of email or + # comment. + # It accepts aliases and takes a block with the formatted content. + # + # Example: + # + # command :my_substitution, :alias_for_my_substitution do |text| + # "#{text} MY AWESOME SUBSTITUTION" + # end + def substitution(*substitution_names, &block) + define_command(SubstitutionDefinition, *substitution_names, &block) + end + + def definition_by_name(name) + command_definitions_by_name[name.to_sym] + end + + private + + def define_command(klass, *command_names, &block) name, *aliases = command_names - definition = CommandDefinition.new( + definition = klass.new( name, aliases: aliases, description: @description, @@ -130,10 +153,6 @@ module Gitlab @condition_block = nil @parse_params_block = nil end - - def definition_by_name(name) - command_definitions_by_name[name.to_sym] - end end end end diff --git a/lib/gitlab/quick_actions/extractor.rb b/lib/gitlab/quick_actions/extractor.rb index 09576be7156..3ebfa3bd4b8 100644 --- a/lib/gitlab/quick_actions/extractor.rb +++ b/lib/gitlab/quick_actions/extractor.rb @@ -46,6 +46,8 @@ module Gitlab end end + content, commands = perform_substitutions(content, commands) + [content.strip, commands] end @@ -110,6 +112,26 @@ module Gitlab }mx end + def perform_substitutions(content, commands) + return unless content + + substitution_definitions = self.command_definitions.select do |definition| + definition.is_a?(Gitlab::QuickActions::SubstitutionDefinition) + end + + substitution_definitions.each do |substitution| + match_data = substitution.match(content) + if match_data + command = [substitution.name.to_s] + command << match_data[1] unless match_data[1].empty? + commands << command + end + content = substitution.perform_substitution(self, content) + end + + [content, commands] + end + def command_names(opts) command_definitions.flat_map do |command| next if command.noop? diff --git a/lib/gitlab/quick_actions/substitution_definition.rb b/lib/gitlab/quick_actions/substitution_definition.rb new file mode 100644 index 00000000000..032c49ed159 --- /dev/null +++ b/lib/gitlab/quick_actions/substitution_definition.rb @@ -0,0 +1,24 @@ +module Gitlab + module QuickActions + class SubstitutionDefinition < CommandDefinition + # noop?=>true means these won't get extracted or removed by Gitlab::QuickActions::Extractor#extract_commands + # QuickActions::InterpretService#perform_substitutions handles them separately + def noop? + true + end + + def match(content) + content.match %r{^/#{all_names.join('|')} ?(.*)$} + end + + def perform_substitution(context, content) + return unless content + + all_names.each do |a_name| + content.gsub!(%r{/#{a_name} ?(.*)$}, execute_block(action_block, context, '\1')) + end + content + end + end + end +end diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb index a4bb3f911d7..ff59dc48bcb 100644 --- a/spec/lib/gitlab/quick_actions/dsl_spec.rb +++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb @@ -42,13 +42,18 @@ describe Gitlab::QuickActions::Dsl do command :with_params_parsing do |parsed| parsed end + + params '<Comment>' + substitution :something do |text| + "#{text} Some complicated thing you want in here" + end end end describe '.command_definitions' do it 'returns an array with commands definitions' do no_args_def, explanation_with_aliases_def, dynamic_description_def, - cc_def, cond_action_def, with_params_parsing_def = + cc_def, cond_action_def, with_params_parsing_def, substitution_def = DummyClass.command_definitions expect(no_args_def.name).to eq(:no_args) @@ -104,6 +109,15 @@ describe Gitlab::QuickActions::Dsl do expect(with_params_parsing_def.condition_block).to be_nil 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(substitution_def.name).to eq(:something) + expect(substitution_def.aliases).to eq([]) + expect(substitution_def.description).to eq('') + expect(substitution_def.explanation).to eq('') + expect(substitution_def.params).to eq(['<Comment>']) + expect(substitution_def.condition_block).to be_nil + 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 end end end diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb index 9d32938e155..f7c288f2393 100644 --- a/spec/lib/gitlab/quick_actions/extractor_spec.rb +++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb @@ -9,6 +9,11 @@ describe Gitlab::QuickActions::Extractor do command(:assign) { } command(:labels) { } command(:power) { } + command(:noop_command) + substitution(:substitution) { 'foo' } + substitution :shrug do |comment| + "#{comment} SHRUG" + end end.command_definitions end @@ -177,6 +182,38 @@ describe Gitlab::QuickActions::Extractor do expect(msg).to eq "hello\nworld" end + it 'does not extract noop commands' do + msg = %(hello\nworld\n/reopen\n/noop_command) + msg, commands = extractor.extract_commands(msg) + + expect(commands).to eq [['reopen']] + expect(msg).to eq "hello\nworld\n/noop_command" + end + + it 'extracts and performs substitution commands' do + msg = %(hello\nworld\n/reopen\n/substitution) + msg, commands = extractor.extract_commands(msg) + + expect(commands).to eq [['reopen'], ['substitution']] + expect(msg).to eq "hello\nworld\nfoo" + end + + it 'extracts and performs substitution commands' do + msg = %(hello\nworld\n/reopen\n/shrug this is great?) + msg, commands = extractor.extract_commands(msg) + + expect(commands).to eq [['reopen'], ['shrug', 'this is great?']] + expect(msg).to eq "hello\nworld\nthis is great? SHRUG" + end + + it 'extracts and performs substitution commands with comments' do + msg = %(hello\nworld\n/reopen\n/substitution wow this is a thing.) + msg, commands = extractor.extract_commands(msg) + + expect(commands).to eq [['reopen'], ['substitution', 'wow this is a thing.']] + expect(msg).to eq "hello\nworld\nfoo" + end + it 'extracts multiple commands' do msg = %(hello\n/power @user.name %9.10 ~"bar baz.2" label\nworld\n/reopen) msg, commands = extractor.extract_commands(msg) diff --git a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb new file mode 100644 index 00000000000..1bb8bc51c96 --- /dev/null +++ b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Gitlab::QuickActions::SubstitutionDefinition do + let(:content) do + <<EOF +Hello! Let's do this! +/sub_name I like this stuff +EOF + end + subject do + described_class.new(:sub_name, action_block: proc { |text| "#{text} foo" }) + end + + describe '#perform_substitution!' do + it 'returns nil if content is nil' do + expect(subject.perform_substitution(self, nil)).to be_nil + end + + it 'performs the substitution by default' do + expect(subject.perform_substitution(self, content)).to eq <<EOF +Hello! Let's do this! +I like this stuff foo +EOF + end + end + + describe '#match' do + it 'checks the content for the command' do + expect(subject.match(content)).to be_truthy + end + + it 'returns the match data' do + data = subject.match(content) + expect(data).to be_a(MatchData) + expect(data[1]).to eq('I like this stuff') + end + + it 'is nil if content does not have the command' do + expect(subject.match('blah')).to be_falsey + end + end +end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 42a4a2591ea..92bde2c92bf 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -9,13 +9,13 @@ describe QuickActions::InterpretService do let(:inprogress) { create(:label, project: project, title: 'In Progress') } let(:bug) { create(:label, project: project, title: 'Bug') } let(:note) { build(:note, commit_id: merge_request.diff_head_sha) } + let(:service) { described_class.new(project, developer) } before do project.team << [developer, :developer] end describe '#execute' do - let(:service) { described_class.new(project, developer) } let(:merge_request) { create(:merge_request, source_project: project) } shared_examples 'reopen command' do @@ -270,6 +270,22 @@ describe QuickActions::InterpretService do end end + shared_examples 'shrug command' do + it 'appends ¯\_(ツ)_/¯ to the comment' do + new_content, _ = service.execute(content, issuable) + + expect(new_content).to end_with(described_class::SHRUG) + end + end + + shared_examples 'tableflip command' do + it 'appends (╯°□°)╯︵ ┻━┻ to the comment' do + new_content, _ = service.execute(content, issuable) + + expect(new_content).to end_with(described_class::TABLEFLIP) + end + end + it_behaves_like 'reopen command' do let(:content) { '/reopen' } let(:issuable) { issue } @@ -775,6 +791,30 @@ describe QuickActions::InterpretService do end end + context '/shrug command' do + it_behaves_like 'shrug command' do + let(:content) { '/shrug people are people' } + let(:issuable) { issue } + end + + it_behaves_like 'shrug command' do + let(:content) { '/shrug' } + let(:issuable) { issue } + end + end + + context '/tableflip command' do + it_behaves_like 'tableflip command' do + let(:content) { '/tableflip curse your sudden but enviable betrayal' } + let(:issuable) { issue } + end + + it_behaves_like 'tableflip command' do + let(:content) { '/tableflip' } + let(:issuable) { issue } + end + end + context '/target_branch command' do let(:non_empty_project) { create(:project, :repository) } let(:another_merge_request) { create(:merge_request, author: developer, source_project: non_empty_project) } |