Commit 3c13e757 authored by Jarka Košanová's avatar Jarka Košanová

Handle epic_id similarly as epic in issue update

- call EpicIssues::CreateService/DestroyService for both cases
- improve EE::Issues::BaseService and use it across services
- improve specs
- add the epic_id para to updateIssue mutation
parent d26183df
...@@ -16550,6 +16550,11 @@ input UpdateIssueInput { ...@@ -16550,6 +16550,11 @@ input UpdateIssueInput {
""" """
dueDate: Time dueDate: Time
"""
The ID of the parent epic. NULL when removing the association
"""
epicId: ID
""" """
The desired health status The desired health status
""" """
......
...@@ -48777,6 +48777,16 @@ ...@@ -48777,6 +48777,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "epicId",
"description": "The ID of the parent epic. NULL when removing the association",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "clientMutationId", "name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.", "description": "A unique identifier for the client performing the mutation.",
...@@ -11,6 +11,10 @@ module EE ...@@ -11,6 +11,10 @@ module EE
::Types::HealthStatusEnum, ::Types::HealthStatusEnum,
required: false, required: false,
description: 'The desired health status' description: 'The desired health status'
argument :epic_id,
GraphQL::ID_TYPE,
required: false,
description: 'The ID of the parent epic. NULL when removing the association'
end end
end end
end end
......
...@@ -5,17 +5,38 @@ module EE ...@@ -5,17 +5,38 @@ module EE
module BaseService module BaseService
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
override :filter_params
def filter_params(issue) def filter_params(issue)
set_epic_param(issue) set_epic_param(issue)
super super
end end
private def handle_epic(issue)
set_epic_param(issue)
return unless params.key?(:epic)
if epic_param
EpicIssues::CreateService.new(epic_param, current_user, { target_issuable: issue }).execute
else
link = EpicIssue.find_by_issue_id(issue.id)
return unless link
EpicIssues::DestroyService.new(link, current_user).execute
end
params.delete(:epic)
end
def set_epic_param(issue) def set_epic_param(issue)
epic = find_epic(issue) return unless epic_param_present?
return unless epic
epic = epic_param || find_epic(issue)
unless epic
params[:epic] = nil
return
end
unless can?(current_user, :admin_epic, epic) unless can?(current_user, :admin_epic, epic)
raise ::Gitlab::Access::AccessDeniedError raise ::Gitlab::Access::AccessDeniedError
...@@ -25,14 +46,22 @@ module EE ...@@ -25,14 +46,22 @@ module EE
end end
def find_epic(issue) def find_epic(issue)
id = params.delete(:epic_id) epic_id = params.delete(:epic_id)
return if id.to_i == 0 return if epic_id.to_i == 0
group = issue.project.group group = issue.project.group
return unless group.present? return unless group.present?
EpicsFinder.new(current_user, group_id: group.id, EpicsFinder.new(current_user, group_id: group.id,
include_ancestor_groups: true).find(id) include_ancestor_groups: true).find(epic_id)
end
def epic_param
params[:epic]
end
def epic_param_present?
params.key?(:epic) || params.key?(:epic_id)
end end
end end
end end
......
...@@ -7,31 +7,15 @@ module EE ...@@ -7,31 +7,15 @@ module EE
override :before_create override :before_create
def before_create(issue) def before_create(issue)
handle_issue_epic_link(issue) handle_epic(issue)
super super
end end
def handle_issue_epic_link(issue) def handle_epic(issue)
return unless params.key?(:epic) issue.confidential = true if epic_param&.confidential
epic = params.delete(:epic) super
if epic
issue.confidential = true if epic.confidential?
EpicIssues::CreateService.new(epic, current_user, { target_issuable: issue }).execute
else
destroy_epic_link(issue)
end
end
def destroy_epic_link(issue)
link = EpicIssue.find_by_issue_id(issue.id)
return unless link
EpicIssues::DestroyService.new(link, current_user).execute
end end
end end
end end
......
...@@ -4,6 +4,7 @@ module EE ...@@ -4,6 +4,7 @@ module EE
module Issues module Issues
module UpdateService module UpdateService
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
include ::Gitlab::Utils::StrongMemoize
override :execute override :execute
def execute(issue) def execute(issue)
...@@ -49,22 +50,6 @@ module EE ...@@ -49,22 +50,6 @@ module EE
Epics::IssuePromoteService.new(issue.project, current_user).execute(issue) Epics::IssuePromoteService.new(issue.project, current_user).execute(issue)
end end
def handle_epic(issue)
return unless params.key?(:epic)
epic_param = params.delete(:epic)
if epic_param
EpicIssues::CreateService.new(epic_param, current_user, { target_issuable: issue }).execute
else
link = EpicIssue.find_by(issue_id: issue.id) # rubocop: disable CodeReuse/ActiveRecord
return unless link
EpicIssues::DestroyService.new(link, current_user).execute
end
end
end end
end end
end end
---
title: Add epic_id param to issue update graphQL mutation
merge_request: 38678
author:
type: added
...@@ -3,8 +3,63 @@ ...@@ -3,8 +3,63 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Mutations::Issues::Update do RSpec.describe Mutations::Issues::Update do
let(:user) { create(:user) }
it_behaves_like 'updating health status' do it_behaves_like 'updating health status' do
let(:resource) { create(:issue) } let(:resource) { create(:issue) }
let(:user) { create(:user) } end
context 'updating parent epic' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:epic) { create(:epic, group: group) }
let(:epic_id) { epic.id }
let(:params) { { project_path: project.full_path, iid: issue.iid, epic_id: epic_id } }
let(:mutated_issue) { subject[:issue] }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
subject { mutation.resolve(params) }
context 'when epics feature is disabled' do
it 'raises an error' do
group.add_developer(user)
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'when epics feature is enabled' do
before do
stub_licensed_features(epics: true)
end
context 'for user without permissions' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'for user with correct permissions' do
before do
group.add_developer(user)
end
context 'when a valid epic is given' do
it 'updates the epic' do
expect(mutated_issue.epic).to eq(epic)
end
end
context 'when nil epic is given' do
let(:epic_id) { nil }
it 'set the epic to nil' do
expect(mutated_issue.epic).to be_nil
end
end
end
end
end end
end end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Issues::UpdateService do RSpec.describe Issues::UpdateService do
let(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:user) { issue.author } let(:user) { issue.author }
...@@ -13,7 +13,7 @@ RSpec.describe Issues::UpdateService do ...@@ -13,7 +13,7 @@ RSpec.describe Issues::UpdateService do
end end
context 'refresh epic dates' do context 'refresh epic dates' do
let(:epic) { create(:epic) } let_it_be(:epic) { create(:epic) }
let(:issue) { create(:issue, epic: epic, project: project) } let(:issue) { create(:issue, epic: epic, project: project) }
context 'updating milestone' do context 'updating milestone' do
...@@ -187,28 +187,39 @@ RSpec.describe Issues::UpdateService do ...@@ -187,28 +187,39 @@ RSpec.describe Issues::UpdateService do
context 'assigning epic' do context 'assigning epic' do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
group.add_maintainer(user)
end end
let(:epic) { create(:epic, group: group) } let(:epic) { create(:epic, group: group) }
subject { update_issue(epic: epic) }
context 'when a user does not have permissions to assign an epic' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
context 'when a user has permissions to assign an epic' do
before do
group.add_maintainer(user)
end
context 'when issue does not belong to an epic yet' do context 'when issue does not belong to an epic yet' do
it 'assigns an issue to the provided epic' do it 'assigns an issue to the provided epic' do
expect { update_issue(epic: epic) }.to change { issue.reload.epic }.from(nil).to(epic) expect { update_issue(epic: epic) }.to change { issue.reload.epic }.from(nil).to(epic)
end end
it 'creates system notes for the epic and the issue' do it 'calls EpicIssues::CreateService' do
expect { update_issue(epic: epic) }.to change { Note.count }.from(0).to(2) link_sevice = double
expect(EpicIssues::CreateService).to receive(:new).with(epic, user, { target_issuable: issue })
.and_return(link_sevice)
expect(link_sevice).to receive(:execute)
epic_note = Note.find_by(noteable_id: epic.id, noteable_type: 'Epic') subject
issue_note = Note.find_by(noteable_id: issue.id, noteable_type: 'Issue')
expect(epic_note.system_note_metadata.action).to eq('epic_issue_added')
expect(issue_note.system_note_metadata.action).to eq('issue_added_to_epic')
end end
end end
context 'when issue does belongs to another epic' do context 'when issue belongs to another epic' do
let(:epic2) { create(:epic, group: group) } let(:epic2) { create(:epic, group: group) }
before do before do
...@@ -216,19 +227,17 @@ RSpec.describe Issues::UpdateService do ...@@ -216,19 +227,17 @@ RSpec.describe Issues::UpdateService do
end end
it 'assigns the issue passed to the provided epic' do it 'assigns the issue passed to the provided epic' do
expect { update_issue(epic: epic) }.to change { issue.reload.epic }.from(epic2).to(epic) expect { subject }.to change { issue.reload.epic }.from(epic2).to(epic)
end end
it 'creates system notes for the epic and the issue' do it 'calls EpicIssues::CreateService' do
expect { update_issue(epic: epic) }.to change { Note.count }.from(0).to(3) link_sevice = double
expect(EpicIssues::CreateService).to receive(:new).with(epic, user, { target_issuable: issue })
epic_note = Note.find_by(noteable_id: epic.id, noteable_type: 'Epic') .and_return(link_sevice)
epic2_note = Note.find_by(noteable_id: epic2.id, noteable_type: 'Epic') expect(link_sevice).to receive(:execute)
issue_note = Note.find_by(noteable_id: issue.id, noteable_type: 'Issue')
expect(epic_note.system_note_metadata.action).to eq('epic_issue_moved') subject
expect(epic2_note.system_note_metadata.action).to eq('epic_issue_moved') end
expect(issue_note.system_note_metadata.action).to eq('issue_changed_epic')
end end
end end
end end
...@@ -236,34 +245,38 @@ RSpec.describe Issues::UpdateService do ...@@ -236,34 +245,38 @@ RSpec.describe Issues::UpdateService do
context 'removing epic' do context 'removing epic' do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
group.add_maintainer(user)
end end
let(:epic) { create(:epic, group: group) } let(:epic) { create(:epic, group: group) }
subject { update_issue(epic: nil) }
context 'when a user has permissions to assign an epic' do
before do
group.add_maintainer(user)
end
context 'when issue does not belong to an epic yet' do context 'when issue does not belong to an epic yet' do
it 'does not do anything' do it 'does not do anything' do
expect { update_issue(epic: nil) }.not_to change { issue.reload.epic } expect { subject }.not_to change { issue.reload.epic }
end end
end end
context 'when issue belongs to an epic' do context 'when issue belongs to an epic' do
before do let!(:epic_issue) { create(:epic_issue, issue: issue, epic: epic)}
issue.update!(epic: epic)
end
it 'assigns a new issue to the provided epic' do it 'unassigns the epic' do
expect { update_issue(epic: nil) }.to change { issue.reload.epic }.from(epic).to(nil) expect { subject }.to change { issue.reload.epic }.from(epic).to(nil)
end end
it 'creates system notes for the epic and the issue' do it 'calls EpicIssues::DestroyService' do
expect { update_issue(epic: nil) }.to change { Note.count }.from(0).to(2) link_sevice = double
expect(EpicIssues::DestroyService).to receive(:new).with(EpicIssue.last, user)
epic_note = Note.find_by(noteable_id: epic.id, noteable_type: 'Epic') .and_return(link_sevice)
issue_note = Note.find_by(noteable_id: issue.id, noteable_type: 'Issue') expect(link_sevice).to receive(:execute)
expect(epic_note.system_note_metadata.action).to eq('epic_issue_removed') subject
expect(issue_note.system_note_metadata.action).to eq('issue_removed_from_epic') end
end end
end end
end end
...@@ -278,6 +291,32 @@ RSpec.describe Issues::UpdateService do ...@@ -278,6 +291,32 @@ RSpec.describe Issues::UpdateService do
let(:epic) { create(:epic, group: group) } let(:epic) { create(:epic, group: group) }
end end
context 'when epic_id is nil' do
before do
stub_licensed_features(epics: true)
group.add_maintainer(user)
end
let(:epic) { create(:epic, group: group) }
let!(:epic_issue) { create(:epic_issue, epic: epic, issue: issue) }
let(:params) { { epic_id: nil } }
subject { update_issue(params) }
it 'removes epic issue link' do
expect { subject }.to change { issue.reload.epic }.from(epic).to(nil)
end
it 'calls EpicIssues::DestroyService' do
link_sevice = double
expect(EpicIssues::DestroyService).to receive(:new).with(epic_issue, user)
.and_return(link_sevice)
expect(link_sevice).to receive(:execute)
subject
end
end
context 'promoting to epic' do context 'promoting to epic' do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
......
...@@ -16,10 +16,10 @@ RSpec.shared_examples 'issue with epic_id parameter' do ...@@ -16,10 +16,10 @@ RSpec.shared_examples 'issue with epic_id parameter' do
context 'when epic_id is 0' do context 'when epic_id is 0' do
let(:params) { { title: 'issue1', epic_id: 0 } } let(:params) { { title: 'issue1', epic_id: 0 } }
it 'ignores epic_id' do it 'does not assign any epic' do
issue = execute issue = execute
expect(issue).to be_persisted expect(issue.reload).to be_persisted
expect(issue.epic).to be_nil expect(issue.epic).to be_nil
end end
end end
...@@ -48,9 +48,17 @@ RSpec.shared_examples 'issue with epic_id parameter' do ...@@ -48,9 +48,17 @@ RSpec.shared_examples 'issue with epic_id parameter' do
it 'creates epic issue link' do it 'creates epic issue link' do
issue = execute issue = execute
expect(issue).to be_persisted expect(issue.reload).to be_persisted
expect(issue.epic).to eq(epic) expect(issue.epic).to eq(epic)
end end
it 'calls EpicIssues::CreateService' do
link_sevice = double
expect(EpicIssues::CreateService).to receive(:new).and_return(link_sevice)
expect(link_sevice).to receive(:execute)
execute
end
end end
context 'when a project is from a subgroup of the epic group' do context 'when a project is from a subgroup of the epic group' do
...@@ -63,7 +71,7 @@ RSpec.shared_examples 'issue with epic_id parameter' do ...@@ -63,7 +71,7 @@ RSpec.shared_examples 'issue with epic_id parameter' do
it 'creates epic issue link' do it 'creates epic issue link' do
issue = execute issue = execute
expect(issue).to be_persisted expect(issue.reload).to be_persisted
expect(issue.epic).to eq(epic) expect(issue.epic).to eq(epic)
end end
end end
......
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