Commit 8c310424 authored by Douwe Maan's avatar Douwe Maan

Merge branch '42545-milestion-quick-actions-for-groups' into 'master'

Resolve "Milestone Quick Action not displayed with no project milestones but with group milestones"

Closes #42545

See merge request gitlab-org/gitlab-ce!17239
parents bb0fe96f ec9cb6ed
...@@ -50,16 +50,7 @@ module Projects ...@@ -50,16 +50,7 @@ module Projects
return [] unless noteable&.is_a?(Issuable) return [] unless noteable&.is_a?(Issuable)
opts = { QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
project: project,
issuable: noteable,
current_user: current_user
}
QuickActions::InterpretService.command_definitions.map do |definition|
next unless definition.available?(opts)
definition.to_h(opts)
end.compact
end end
end end
end end
...@@ -7,6 +7,18 @@ module QuickActions ...@@ -7,6 +7,18 @@ module QuickActions
SHRUG = '¯\\_(ツ)_/¯'.freeze SHRUG = '¯\\_(ツ)_/¯'.freeze
TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
# Takes an issuable and returns an array of all the available commands
# represented with .to_h
def available_commands(issuable)
@issuable = issuable
self.class.command_definitions.map do |definition|
next unless definition.available?(self)
definition.to_h(self)
end.compact
end
# 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) def execute(content, issuable)
...@@ -15,8 +27,8 @@ module QuickActions ...@@ -15,8 +27,8 @@ module QuickActions
@issuable = issuable @issuable = issuable
@updates = {} @updates = {}
content, commands = extractor.extract_commands(content, context) content, commands = extractor.extract_commands(content)
extract_updates(commands, context) extract_updates(commands)
[content, @updates] [content, @updates]
end end
...@@ -28,8 +40,8 @@ module QuickActions ...@@ -28,8 +40,8 @@ module QuickActions
@issuable = issuable @issuable = issuable
content, commands = extractor.extract_commands(content, context) content, commands = extractor.extract_commands(content)
commands = explain_commands(commands, context) commands = explain_commands(commands)
[content, commands] [content, commands]
end end
...@@ -157,11 +169,11 @@ module QuickActions ...@@ -157,11 +169,11 @@ module QuickActions
params '%"milestone"' params '%"milestone"'
condition do condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project) && current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
project.milestones.active.any? find_milestones(project, state: 'active').any?
end end
parse_params do |milestone_param| parse_params do |milestone_param|
extract_references(milestone_param, :milestone).first || extract_references(milestone_param, :milestone).first ||
project.milestones.find_by(title: milestone_param.strip) find_milestones(project, title: milestone_param.strip).first
end end
command :milestone do |milestone| command :milestone do |milestone|
@updates[:milestone_id] = milestone.id if milestone @updates[:milestone_id] = milestone.id if milestone
...@@ -544,6 +556,10 @@ module QuickActions ...@@ -544,6 +556,10 @@ module QuickActions
users users
end end
def find_milestones(project, params = {})
MilestonesFinder.new(params.merge(project_ids: [project.id], group_ids: [project.group&.id])).execute
end
def find_labels(labels_param) def find_labels(labels_param)
extract_references(labels_param, :label) | extract_references(labels_param, :label) |
LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute
...@@ -557,21 +573,21 @@ module QuickActions ...@@ -557,21 +573,21 @@ module QuickActions
find_labels(labels_param).map(&:id) find_labels(labels_param).map(&:id)
end end
def explain_commands(commands, opts) def explain_commands(commands)
commands.map do |name, arg| commands.map do |name, arg|
definition = self.class.definition_by_name(name) definition = self.class.definition_by_name(name)
next unless definition next unless definition
definition.explain(self, opts, arg) definition.explain(self, arg)
end.compact end.compact
end end
def extract_updates(commands, opts) def extract_updates(commands)
commands.each do |name, arg| commands.each do |name, arg|
definition = self.class.definition_by_name(name) definition = self.class.definition_by_name(name)
next unless definition next unless definition
definition.execute(self, opts, arg) definition.execute(self, arg)
end end
end end
...@@ -581,14 +597,5 @@ module QuickActions ...@@ -581,14 +597,5 @@ module QuickActions
ext.references(type) ext.references(type)
end end
def context
{
issuable: issuable,
current_user: current_user,
project: project,
params: params
}
end
end end
end end
---
title: Allows the usage of /milestone quick action for group milestones
merge_request: 17239
author: Jacopo Beschi @jacopo-beschi
type: fixed
...@@ -24,15 +24,14 @@ module Gitlab ...@@ -24,15 +24,14 @@ module Gitlab
action_block.nil? action_block.nil?
end end
def available?(opts) def available?(context)
return true unless condition_block return true unless condition_block
context = OpenStruct.new(opts)
context.instance_exec(&condition_block) context.instance_exec(&condition_block)
end end
def explain(context, opts, arg) def explain(context, arg)
return unless available?(opts) return unless available?(context)
if explanation.respond_to?(:call) if explanation.respond_to?(:call)
execute_block(explanation, context, arg) execute_block(explanation, context, arg)
...@@ -41,15 +40,13 @@ module Gitlab ...@@ -41,15 +40,13 @@ module Gitlab
end end
end end
def execute(context, opts, arg) def execute(context, arg)
return if noop? || !available?(opts) return if noop? || !available?(context)
execute_block(action_block, context, arg) execute_block(action_block, context, arg)
end end
def to_h(opts) def to_h(context)
context = OpenStruct.new(opts)
desc = description desc = description
if desc.respond_to?(:call) if desc.respond_to?(:call)
desc = context.instance_exec(&desc) rescue '' desc = context.instance_exec(&desc) rescue ''
......
...@@ -62,9 +62,8 @@ module Gitlab ...@@ -62,9 +62,8 @@ module Gitlab
# 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 given to # It accepts a block that will be evaluated with the context
# `CommandDefintion#to_h`. # of a QuickActions::InterpretService instance
#
# Example: # Example:
# #
# condition do # condition do
......
...@@ -29,7 +29,7 @@ module Gitlab ...@@ -29,7 +29,7 @@ module Gitlab
# commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']] # commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']]
# msg #=> "hello\nworld" # msg #=> "hello\nworld"
# ``` # ```
def extract_commands(content, opts = {}) def extract_commands(content)
return [content, []] unless content return [content, []] unless content
content = content.dup content = content.dup
...@@ -37,7 +37,7 @@ module Gitlab ...@@ -37,7 +37,7 @@ module Gitlab
commands = [] commands = []
content.delete!("\r") content.delete!("\r")
content.gsub!(commands_regex(opts)) do content.gsub!(commands_regex) do
if $~[:cmd] if $~[:cmd]
commands << [$~[:cmd], $~[:arg]].reject(&:blank?) commands << [$~[:cmd], $~[:arg]].reject(&:blank?)
'' ''
...@@ -60,8 +60,8 @@ module Gitlab ...@@ -60,8 +60,8 @@ module Gitlab
# It looks something like: # It looks something like:
# #
# /^\/(?<cmd>close|reopen|...)(?:( |$))(?<arg>[^\/\n]*)(?:\n|$)/ # /^\/(?<cmd>close|reopen|...)(?:( |$))(?<arg>[^\/\n]*)(?:\n|$)/
def commands_regex(opts) def commands_regex
names = command_names(opts).map(&:to_s) names = command_names.map(&:to_s)
@commands_regex ||= %r{ @commands_regex ||= %r{
(?<code> (?<code>
...@@ -133,7 +133,7 @@ module Gitlab ...@@ -133,7 +133,7 @@ module Gitlab
[content, commands] [content, commands]
end end
def command_names(opts) def command_names
command_definitions.flat_map do |command| command_definitions.flat_map do |command|
next if command.noop? next if command.noop?
......
...@@ -40,7 +40,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -40,7 +40,7 @@ describe Gitlab::QuickActions::CommandDefinition do
end end
describe "#available?" do describe "#available?" do
let(:opts) { { go: false } } let(:opts) { OpenStruct.new(go: false) }
context "when the command has a condition block" do context "when the command has a condition block" do
before do before do
...@@ -78,7 +78,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -78,7 +78,7 @@ describe Gitlab::QuickActions::CommandDefinition do
it "doesn't execute the command" do it "doesn't execute the command" do
expect(context).not_to receive(:instance_exec) expect(context).not_to receive(:instance_exec)
subject.execute(context, {}, nil) subject.execute(context, nil)
expect(context.run).to be false expect(context.run).to be false
end end
...@@ -95,7 +95,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -95,7 +95,7 @@ describe Gitlab::QuickActions::CommandDefinition do
end end
it "doesn't execute the command" do it "doesn't execute the command" do
subject.execute(context, {}, nil) subject.execute(context, nil)
expect(context.run).to be false expect(context.run).to be false
end end
...@@ -109,7 +109,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -109,7 +109,7 @@ describe Gitlab::QuickActions::CommandDefinition do
context "when the command is provided an argument" do context "when the command is provided an argument" do
it "executes the command" do it "executes the command" do
subject.execute(context, {}, true) subject.execute(context, true)
expect(context.run).to be true expect(context.run).to be true
end end
...@@ -117,7 +117,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -117,7 +117,7 @@ describe Gitlab::QuickActions::CommandDefinition do
context "when the command is not provided an argument" do context "when the command is not provided an argument" do
it "executes the command" do it "executes the command" do
subject.execute(context, {}, nil) subject.execute(context, nil)
expect(context.run).to be true expect(context.run).to be true
end end
...@@ -131,7 +131,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -131,7 +131,7 @@ describe Gitlab::QuickActions::CommandDefinition do
context "when the command is provided an argument" do context "when the command is provided an argument" do
it "executes the command" do it "executes the command" do
subject.execute(context, {}, true) subject.execute(context, true)
expect(context.run).to be true expect(context.run).to be true
end end
...@@ -139,7 +139,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -139,7 +139,7 @@ describe Gitlab::QuickActions::CommandDefinition do
context "when the command is not provided an argument" do context "when the command is not provided an argument" do
it "doesn't execute the command" do it "doesn't execute the command" do
subject.execute(context, {}, nil) subject.execute(context, nil)
expect(context.run).to be false expect(context.run).to be false
end end
...@@ -153,7 +153,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -153,7 +153,7 @@ describe Gitlab::QuickActions::CommandDefinition do
context "when the command is provided an argument" do context "when the command is provided an argument" do
it "executes the command" do it "executes the command" do
subject.execute(context, {}, true) subject.execute(context, true)
expect(context.run).to be true expect(context.run).to be true
end end
...@@ -161,7 +161,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -161,7 +161,7 @@ describe Gitlab::QuickActions::CommandDefinition do
context "when the command is not provided an argument" do context "when the command is not provided an argument" do
it "executes the command" do it "executes the command" do
subject.execute(context, {}, nil) subject.execute(context, nil)
expect(context.run).to be true expect(context.run).to be true
end end
...@@ -175,7 +175,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -175,7 +175,7 @@ describe Gitlab::QuickActions::CommandDefinition do
end end
it 'executes the command passing the parsed param' do it 'executes the command passing the parsed param' do
subject.execute(context, {}, 'something ') subject.execute(context, 'something ')
expect(context.received_arg).to eq('something') expect(context.received_arg).to eq('something')
end end
...@@ -192,7 +192,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -192,7 +192,7 @@ describe Gitlab::QuickActions::CommandDefinition do
end end
it 'returns nil' do it 'returns nil' do
result = subject.explain({}, {}, nil) result = subject.explain({}, nil)
expect(result).to be_nil expect(result).to be_nil
end end
...@@ -204,7 +204,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -204,7 +204,7 @@ describe Gitlab::QuickActions::CommandDefinition do
end end
it 'returns this static string' do it 'returns this static string' do
result = subject.explain({}, {}, nil) result = subject.explain({}, nil)
expect(result).to eq 'Explanation' expect(result).to eq 'Explanation'
end end
...@@ -216,7 +216,7 @@ describe Gitlab::QuickActions::CommandDefinition do ...@@ -216,7 +216,7 @@ describe Gitlab::QuickActions::CommandDefinition do
end end
it 'invokes the proc' do it 'invokes the proc' do
result = subject.explain({}, {}, 'explanation') result = subject.explain({}, 'explanation')
expect(result).to eq 'Dynamic explanation' expect(result).to eq 'Dynamic explanation'
end end
......
...@@ -76,7 +76,7 @@ describe Gitlab::QuickActions::Dsl do ...@@ -76,7 +76,7 @@ describe Gitlab::QuickActions::Dsl do
expect(dynamic_description_def.name).to eq(:dynamic_description) expect(dynamic_description_def.name).to eq(:dynamic_description)
expect(dynamic_description_def.aliases).to eq([]) expect(dynamic_description_def.aliases).to eq([])
expect(dynamic_description_def.to_h(noteable: 'issue')[:description]).to eq('A dynamic description for ISSUE') expect(dynamic_description_def.to_h(OpenStruct.new(noteable: 'issue'))[:description]).to eq('A dynamic description for ISSUE')
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
......
...@@ -522,6 +522,22 @@ describe QuickActions::InterpretService do ...@@ -522,6 +522,22 @@ describe QuickActions::InterpretService do
let(:issuable) { merge_request } let(:issuable) { merge_request }
end end
context 'only group milestones available' do
let(:group) { create(:group) }
let(:project) { create(:project, :public, namespace: group) }
let(:milestone) { create(:milestone, group: group, title: '10.0') }
it_behaves_like 'milestone command' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { issue }
end
it_behaves_like 'milestone command' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { merge_request }
end
end
it_behaves_like 'remove_milestone command' do it_behaves_like 'remove_milestone command' do
let(:content) { '/remove_milestone' } let(:content) { '/remove_milestone' }
let(:issuable) { issue } let(:issuable) { issue }
......
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