Commit 83cbca45 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'issue_293842' into 'master'

Support /child_epic quick action when creating epics

See merge request gitlab-org/gitlab!53073
parents 4caab3a8 27ca980b
...@@ -4,7 +4,7 @@ module Epics ...@@ -4,7 +4,7 @@ module Epics
class BaseService < IssuableBaseService class BaseService < IssuableBaseService
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
attr_reader :group, :parent_epic attr_reader :group, :parent_epic, :child_epic
def initialize(group, current_user, params = {}) def initialize(group, current_user, params = {})
@group, @current_user, @params = group, current_user, params @group, @current_user, @params = group, current_user, params
...@@ -21,6 +21,7 @@ module Epics ...@@ -21,6 +21,7 @@ module Epics
def set_quick_action_params def set_quick_action_params
@parent_epic = params.delete(:quick_action_assign_to_parent_epic) @parent_epic = params.delete(:quick_action_assign_to_parent_epic)
@child_epic = params.delete(:quick_action_assign_child_epic)
end end
def assign_parent_epic_for(epic) def assign_parent_epic_for(epic)
...@@ -29,6 +30,12 @@ module Epics ...@@ -29,6 +30,12 @@ module Epics
EpicLinks::CreateService.new(parent_epic, current_user, { target_issuable: epic }).execute EpicLinks::CreateService.new(parent_epic, current_user, { target_issuable: epic }).execute
end end
def assign_child_epic_for(epic)
return unless child_epic
EpicLinks::CreateService.new(epic, current_user, { target_issuable: child_epic }).execute
end
def available_labels def available_labels
@available_labels ||= LabelsFinder.new( @available_labels ||= LabelsFinder.new(
current_user, current_user,
......
...@@ -24,6 +24,7 @@ module Epics ...@@ -24,6 +24,7 @@ module Epics
def after_create(epic) def after_create(epic)
assign_parent_epic_for(epic) assign_parent_epic_for(epic)
assign_child_epic_for(epic)
end end
def set_date_params def set_date_params
......
...@@ -22,6 +22,7 @@ module Epics ...@@ -22,6 +22,7 @@ module Epics
end end
assign_parent_epic_for(epic) assign_parent_epic_for(epic)
assign_child_epic_for(epic)
epic epic
end end
......
---
title: Support /child quick action when creating epics
merge_request: 53073
author:
type: added
...@@ -15,12 +15,12 @@ module EE ...@@ -15,12 +15,12 @@ module EE
_("Adds %{epic_ref} as child epic.") % { epic_ref: child_epic.to_reference(quick_action_target) } if child_epic _("Adds %{epic_ref} as child epic.") % { epic_ref: child_epic.to_reference(quick_action_target) } if child_epic
end end
types Epic types Epic
condition { action_allowed_only_on_update? } condition { action_allowed? }
params '<&epic | group&epic | Epic URL>' params '<&epic | group&epic | Epic URL>'
command :child_epic do |epic_param| command :child_epic do |epic_param|
child_epic = extract_epic(epic_param) child_epic = extract_epic(epic_param)
@execution_message[:child_epic] = add_child_epic(quick_action_target, child_epic) @execution_message[:child_epic] = set_child_epic_update(quick_action_target, child_epic)
end end
desc _('Remove child epic from an epic') desc _('Remove child epic from an epic')
...@@ -102,12 +102,12 @@ module EE ...@@ -102,12 +102,12 @@ module EE
epic.child?(target_epic.id) || target_epic.child?(epic.id) epic.child?(target_epic.id) || target_epic.child?(epic.id)
end end
def add_child_epic(target_epic, child_epic) def set_child_epic_update(target_epic, child_epic)
return child_error_message(:not_present) unless child_epic.present? return child_error_message(:not_present) unless child_epic.present?
return child_error_message(:already_related) if epics_related?(child_epic, target_epic) return child_error_message(:already_related) if epics_related?(child_epic, target_epic)
return child_error_message(:no_permission) unless current_user.can?(:read_epic, child_epic) return child_error_message(:no_permission) unless current_user.can?(:read_epic, child_epic)
EpicLinks::CreateService.new(target_epic, current_user, { target_issuable: child_epic }).execute @updates[:quick_action_assign_child_epic] = child_epic
_("Added %{epic_ref} as a child epic.") % { epic_ref: child_epic.to_reference(target_epic) } _("Added %{epic_ref} as a child epic.") % { epic_ref: child_epic.to_reference(target_epic) }
end end
......
...@@ -84,12 +84,12 @@ RSpec.describe Epics::CreateService do ...@@ -84,12 +84,12 @@ RSpec.describe Epics::CreateService do
end end
context 'when description param has quick action' do context 'when description param has quick action' do
context 'for /parent_epic' do before do
before do stub_licensed_features(epics: true, subepics: true)
stub_licensed_features(epics: true, subepics: true) group.add_developer(user)
group.add_developer(user) end
end
context 'for /parent_epic' do
it 'assigns parent epic' do it 'assigns parent epic' do
parent_epic = create(:epic, group: group) parent_epic = create(:epic, group: group)
description = "/parent_epic #{parent_epic.to_reference}" description = "/parent_epic #{parent_epic.to_reference}"
...@@ -113,6 +113,31 @@ RSpec.describe Epics::CreateService do ...@@ -113,6 +113,31 @@ RSpec.describe Epics::CreateService do
end end
end end
end end
context 'for /child_epic' do
it 'sets a child epic' do
child_epic = create(:epic, group: group)
description = "/child_epic #{child_epic.to_reference}"
params = { title: 'New epic with child', description: description }
epic = described_class.new(group, user, params).execute
expect(epic.reload.children).to include(child_epic)
end
context 'when child epic cannot be assigned' do
it 'does not set child epic' do
other_group = create(:group, :private)
child_epic = create(:epic, group: other_group)
description = "/child_epic #{child_epic.to_reference(group)}"
params = { title: 'New epic with child', description: description }
epic = described_class.new(group, user, params).execute
expect(epic.reload.children).to be_empty
end
end
end
end end
end end
end end
......
...@@ -308,6 +308,11 @@ RSpec.describe Epics::UpdateService do ...@@ -308,6 +308,11 @@ RSpec.describe Epics::UpdateService do
end end
context 'with quick actions in the description' do context 'with quick actions in the description' do
before do
stub_licensed_features(epics: true, subepics: true)
group.add_developer(user)
end
context 'for /label' do context 'for /label' do
let(:label) { create(:group_label, group: group) } let(:label) { create(:group_label, group: group) }
...@@ -319,11 +324,6 @@ RSpec.describe Epics::UpdateService do ...@@ -319,11 +324,6 @@ RSpec.describe Epics::UpdateService do
end end
context 'for /parent_epic' do context 'for /parent_epic' do
before do
stub_licensed_features(epics: true, subepics: true)
group.add_developer(user)
end
it 'assigns parent epic' do it 'assigns parent epic' do
parent_epic = create(:epic, group: epic.group) parent_epic = create(:epic, group: epic.group)
...@@ -343,6 +343,26 @@ RSpec.describe Epics::UpdateService do ...@@ -343,6 +343,26 @@ RSpec.describe Epics::UpdateService do
end end
end end
end end
context 'for /child_epic' do
it 'sets a child epic' do
child_epic = create(:epic, group: group)
update_epic(description: "/child_epic #{child_epic.to_reference}")
expect(epic.reload.children).to include(child_epic)
end
context 'when child epic cannot be assigned' do
it 'does not set child epic' do
other_group = create(:group, :private)
child_epic = create(:epic, group: other_group)
update_epic(description: "/child_epic #{child_epic.to_reference(group)}")
expect(epic.reload.children).to be_empty
end
end
end
end end
end end
end end
...@@ -35,6 +35,57 @@ RSpec.describe QuickActions::InterpretService do ...@@ -35,6 +35,57 @@ RSpec.describe QuickActions::InterpretService do
end end
end end
shared_examples 'adds quick action parameter' do |parameter_key, quick_action|
let(:content) { "/#{quick_action} #{referenced_epic&.to_reference(epic)}" }
it 'adds parameter to updates array' do
_, updates = service.execute(content, epic)
expect(updates[parameter_key]).to eq(referenced_epic)
end
end
shared_examples 'does not add quick action parameter' do |parameter_key, quick_action|
let(:content) { "/#{quick_action} #{referenced_epic&.to_reference(epic)}" }
it 'does not add parameter to updates array' do
_, updates = service.execute(content, epic)
expect(updates[parameter_key]).to eq(nil)
end
end
shared_examples 'returns execution messages' do |relation|
context 'when correct epic reference' do
let(:content) { "/#{relation}_epic #{epic2&.to_reference(epic)}" }
let(:explain_action) { relation == :child ? 'Adds' : 'Sets'}
let(:execute_action) { relation == :child ? 'Added' : 'Set'}
let(:article) { relation == :child ? 'a' : 'the'}
it 'returns explain message with epic reference' do
_, explanations = service.explain(content, epic)
expect(explanations)
.to eq(["#{explain_action} #{epic2.group.name}&#{epic2.iid} as #{relation} epic."])
end
it 'returns successful execution message' do
_, _, message = service.execute(content, epic)
expect(message)
.to eq("#{execute_action} #{epic2.group.name}&#{epic2.iid} as #{article} #{relation} epic.")
end
end
context 'when epic reference is wrong' do |relation|
let(:content) { "/#{relation}_epic qwe" }
it 'returns empty explain message' do
_, explanations = service.explain(content, epic)
expect(explanations).to eq([])
end
end
end
describe '#execute' do describe '#execute' do
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
...@@ -537,25 +588,7 @@ RSpec.describe QuickActions::InterpretService do ...@@ -537,25 +588,7 @@ RSpec.describe QuickActions::InterpretService do
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
let(:epic) { create(:epic, group: group) } let(:epic) { create(:epic, group: group) }
let(:child_epic) { create(:epic, group: group) } let(:child_epic) { create(:epic, group: group) }
let(:content) { "/child_epic #{child_epic&.to_reference(epic)}" } let(:referenced_epic) { child_epic }
shared_examples 'epic relation is not added' do
it 'does not add child epic to epic' do
service.execute(content, epic)
child_epic.reload
expect(child_epic.parent).to be_nil
end
end
shared_examples 'epic relation is added' do
it 'adds child epic relation to the epic' do
service.execute(content, epic)
child_epic.reload
expect(child_epic.parent).to eq(epic)
end
end
context 'when subepics are enabled' do context 'when subepics are enabled' do
before do before do
...@@ -563,7 +596,7 @@ RSpec.describe QuickActions::InterpretService do ...@@ -563,7 +596,7 @@ RSpec.describe QuickActions::InterpretService do
end end
context 'when a user does not have permissions to add epic relations' do context 'when a user does not have permissions to add epic relations' do
it_behaves_like 'epic relation is not added' it_behaves_like 'does not add quick action parameter', :quick_action_assign_child_epic, :child_epic
it_behaves_like 'quick action is unavailable', :child_epic do it_behaves_like 'quick action is unavailable', :child_epic do
let(:target) { epic } let(:target) { epic }
end end
...@@ -575,7 +608,7 @@ RSpec.describe QuickActions::InterpretService do ...@@ -575,7 +608,7 @@ RSpec.describe QuickActions::InterpretService do
another_group.add_developer(current_user) another_group.add_developer(current_user)
end end
it_behaves_like 'epic relation is added' it_behaves_like 'adds quick action parameter', :quick_action_assign_child_epic, :child_epic
it_behaves_like 'quick action is available', :child_epic do it_behaves_like 'quick action is available', :child_epic do
let(:target) { epic } let(:target) { epic }
...@@ -590,63 +623,58 @@ RSpec.describe QuickActions::InterpretService do ...@@ -590,63 +623,58 @@ RSpec.describe QuickActions::InterpretService do
end end
context 'when target epic is not persisted yet' do context 'when target epic is not persisted yet' do
let(:target) { build(:epic, group: group) } let(:epic) { build(:epic, group: group) }
it_behaves_like 'quick action is unavailable', :child_epic it_behaves_like 'adds quick action parameter', :quick_action_assign_child_epic, :child_epic
end end
context 'when passed child epic is nil' do context 'when passed child epic is nil' do
let(:child_epic) { nil } let(:child_epic) { nil }
it 'does not add child epic to epic' do it_behaves_like 'does not add quick action parameter', :quick_action_assign_child_epic, :child_epic
expect { service.execute(content, epic) }.not_to change { epic.children.count }
expect { service.execute(content, epic) }.not_to raise_error
end
it 'does not raise error' do it 'does not raise error' do
content = "/child_epic "
expect { service.execute(content, epic) }.not_to raise_error expect { service.execute(content, epic) }.not_to raise_error
end end
end end
context 'when child_epic is already linked to an epic' do context 'when child_epic is already linked to an epic' do
let(:another_epic) { create(:epic, group: group) } let(:referenced_epic) { create(:epic, group: group) }
before do before do
child_epic.update!(parent: another_epic) child_epic.update!(parent: referenced_epic)
end end
it_behaves_like 'epic relation is added'
it_behaves_like 'quick action is available', :child_epic do it_behaves_like 'quick action is available', :child_epic do
let(:target) { epic } let(:target) { epic }
end end
end end
context 'when child epic is in a subgroup of parent epic' do context 'when child epic is in a subgroup of parent epic' do
let(:child_epic) { create(:epic, group: subgroup) } let(:referenced_epic) { create(:epic, group: subgroup) }
it_behaves_like 'epic relation is added'
it_behaves_like 'quick action is available', :child_epic do it_behaves_like 'quick action is available', :child_epic do
let(:target) { epic } let(:target) { epic }
end end
end end
context 'when child epic is in a parent group of the parent epic' do context 'when child epic is in a parent group of the parent epic' do
let(:child_epic) { create(:epic, group: group) } let(:referenced_epic) { create(:epic, group: group) }
before do before do
epic.update!(group: subgroup) epic.update!(group: subgroup)
end end
it_behaves_like 'epic relation is not added'
it_behaves_like 'quick action is available', :child_epic do it_behaves_like 'quick action is available', :child_epic do
let(:target) { epic } let(:target) { epic }
end end
end end
context 'when child epic is in a different group than parent epic' do context 'when child epic is in a different group than parent epic' do
let(:child_epic) { create(:epic, group: another_group) } let(:referenced_epic) { create(:epic, group: another_group) }
it_behaves_like 'epic relation is not added'
it_behaves_like 'quick action is available', :child_epic do it_behaves_like 'quick action is available', :child_epic do
let(:target) { epic } let(:target) { epic }
end end
...@@ -659,7 +687,7 @@ RSpec.describe QuickActions::InterpretService do ...@@ -659,7 +687,7 @@ RSpec.describe QuickActions::InterpretService do
group.add_developer(current_user) group.add_developer(current_user)
end end
it_behaves_like 'epic relation is not added' it_behaves_like 'does not add quick action parameter', :quick_action_assign_child_epic, :child_epic
it_behaves_like 'quick action is unavailable', :child_epic do it_behaves_like 'quick action is unavailable', :child_epic do
let(:target) { epic } let(:target) { epic }
end end
...@@ -1112,47 +1140,6 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1112,47 +1140,6 @@ RSpec.describe QuickActions::InterpretService do
group.add_developer(current_user) group.add_developer(current_user)
end end
shared_examples 'quick action parameters' do |parameter, quick_action|
let(:content) { "/#{quick_action} #{epic2&.to_reference(epic)}" }
it 'adds parameter to updates array' do
_, updates = service.execute(content, epic)
expect(updates[:quick_action_assign_to_parent_epic]).to eq(epic2)
end
end
shared_examples 'returning execution messages' do |relation|
context 'when correct epic reference' do
let(:content) { "/#{relation}_epic #{epic2&.to_reference(epic)}" }
let(:explain_action) { relation == :child ? 'Adds' : 'Sets'}
let(:execute_action) { relation == :child ? 'Added' : 'Set'}
let(:article) { relation == :child ? 'a' : 'the'}
it 'returns explain message with epic reference' do
_, explanations = service.explain(content, epic)
expect(explanations)
.to eq(["#{explain_action} #{epic2.group.name}&#{epic2.iid} as #{relation} epic."])
end
it 'returns successful execution message' do
_, _, message = service.execute(content, epic)
expect(message)
.to eq("#{execute_action} #{epic2.group.name}&#{epic2.iid} as #{article} #{relation} epic.")
end
end
context 'when epic reference is wrong' do |relation|
let(:content) { "/#{relation}_epic qwe" }
it 'returns empty explain message' do
_, explanations = service.explain(content, epic)
expect(explanations).to eq([])
end
end
end
shared_examples 'target epic does not exist' do |relation| shared_examples 'target epic does not exist' do |relation|
it 'returns unsuccessful execution message' do it 'returns unsuccessful execution message' do
_, _, message = service.execute(content, epic) _, _, message = service.execute(content, epic)
...@@ -1181,7 +1168,7 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1181,7 +1168,7 @@ RSpec.describe QuickActions::InterpretService do
end end
context 'child_epic command' do context 'child_epic command' do
it_behaves_like 'returning execution messages', :child it_behaves_like 'returns execution messages', :child
context 'when epic is already a child epic' do context 'when epic is already a child epic' do
let(:content) { "/child_epic #{epic2&.to_reference(epic)}" } let(:content) { "/child_epic #{epic2&.to_reference(epic)}" }
...@@ -1270,8 +1257,10 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1270,8 +1257,10 @@ RSpec.describe QuickActions::InterpretService do
end end
context 'parent_epic command' do context 'parent_epic command' do
it_behaves_like 'quick action parameters', :quick_action_assign_to_parent_epic, :parent_epic let(:referenced_epic) { epic2 }
it_behaves_like 'returning execution messages', :parent
it_behaves_like 'adds quick action parameter', :quick_action_assign_to_parent_epic, :parent_epic
it_behaves_like 'returns execution messages', :parent
context 'when epic is already a parent epic' do context 'when epic is already a parent epic' do
let(:content) { "/parent_epic #{epic2&.to_reference(epic)}" } let(:content) { "/parent_epic #{epic2&.to_reference(epic)}" }
...@@ -1301,8 +1290,9 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1301,8 +1290,9 @@ RSpec.describe QuickActions::InterpretService do
context 'when target epic is not persisted yet' do context 'when target epic is not persisted yet' do
let(:epic) { build(:epic, group: group) } let(:epic) { build(:epic, group: group) }
let(:referenced_epic) { epic2 }
it_behaves_like 'quick action parameters', :quick_action_assign_to_parent_epic, :parent_epic it_behaves_like 'adds quick action parameter', :quick_action_assign_to_parent_epic, :parent_epic
end end
context 'when user has no permission to read epic' do context 'when user has no permission to read epic' do
......
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