Merge branch '29385/add_shrug_command' into 'master'
Fixes #29385: Add /shrug and /tableflip commands Closes #29385 See merge request !10068
This commit is contained in:
commit
0d52e59de4
9 changed files with 225 additions and 7 deletions
|
@ -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'
|
||||
|
|
4
changelogs/unreleased/29385-add_shrug_command.yml
Normal file
4
changelogs/unreleased/29385-add_shrug_command.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add /shrug and /tableflip commands
|
||||
merge_request: 10068
|
||||
author: Alex Ives
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
24
lib/gitlab/quick_actions/substitution_definition.rb
Normal file
24
lib/gitlab/quick_actions/substitution_definition.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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) }
|
||||
|
|
Loading…
Reference in a new issue