Commit 2812bba0 authored by Dmytro Zaporozhets (DZ)'s avatar Dmytro Zaporozhets (DZ)

Merge branch 'epic_board_destroy' into 'master'

Allow to delete epic boards

See merge request gitlab-org/gitlab!56670
parents 57130a7e d1cea4d8
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Boards module Boards
class DestroyService < Boards::BaseService class DestroyService < Boards::BaseService
def execute(board) def execute(board)
if parent.boards.size == 1 if boards.size == 1
return ServiceResponse.error(message: "The board could not be deleted, because the parent doesn't have any other boards.") return ServiceResponse.error(message: "The board could not be deleted, because the parent doesn't have any other boards.")
end end
...@@ -11,5 +11,11 @@ module Boards ...@@ -11,5 +11,11 @@ module Boards
ServiceResponse.success ServiceResponse.success
end end
private
def boards
parent.boards
end
end end
end end
...@@ -2372,6 +2372,16 @@ Autogenerated return type of DestroyContainerRepositoryTags. ...@@ -2372,6 +2372,16 @@ Autogenerated return type of DestroyContainerRepositoryTags.
| `deletedTagNames` | [`[String!]!`](#string) | Deleted container repository tags. | | `deletedTagNames` | [`[String!]!`](#string) | Deleted container repository tags. |
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `DestroyEpicBoardPayload`
Autogenerated return type of DestroyEpicBoard.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| `epicBoard` | [`EpicBoard`](#epicboard) | Epic board after mutation. |
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `DestroyNotePayload` ### `DestroyNotePayload`
Autogenerated return type of DestroyNote. Autogenerated return type of DestroyNote.
......
...@@ -39,8 +39,9 @@ module EE ...@@ -39,8 +39,9 @@ module EE
mount_mutation ::Mutations::Boards::Update mount_mutation ::Mutations::Boards::Update
mount_mutation ::Mutations::Boards::UpdateEpicUserPreferences mount_mutation ::Mutations::Boards::UpdateEpicUserPreferences
mount_mutation ::Mutations::Boards::EpicBoards::Create mount_mutation ::Mutations::Boards::EpicBoards::Create
mount_mutation ::Mutations::Boards::EpicBoards::Update mount_mutation ::Mutations::Boards::EpicBoards::Destroy
mount_mutation ::Mutations::Boards::EpicBoards::EpicMoveList mount_mutation ::Mutations::Boards::EpicBoards::EpicMoveList
mount_mutation ::Mutations::Boards::EpicBoards::Update
mount_mutation ::Mutations::Boards::EpicLists::Create mount_mutation ::Mutations::Boards::EpicLists::Create
mount_mutation ::Mutations::Boards::Lists::UpdateLimitMetrics mount_mutation ::Mutations::Boards::Lists::UpdateLimitMetrics
mount_mutation ::Mutations::InstanceSecurityDashboard::AddProject mount_mutation ::Mutations::InstanceSecurityDashboard::AddProject
......
# frozen_string_literal: true
module Mutations
module Boards
module EpicBoards
class Destroy < ::Mutations::BaseMutation
graphql_name 'DestroyEpicBoard'
field :epic_board,
Types::Boards::EpicBoardType,
null: true,
description: 'Epic board after mutation.'
argument :id,
::Types::GlobalIDType[::Boards::EpicBoard],
required: true,
description: 'Global ID of the board to destroy.'
authorize :admin_epic_board
def resolve(id:)
board = authorized_find!(id: id)
response = ::Boards::EpicBoards::DestroyService.new(board.resource_parent, current_user).execute(board)
{
epic_board: response.success? ? nil : board,
errors: response.errors
}
end
private
def find_object(id:)
GitlabSchema.find_by_gid(id)
end
end
end
end
end
# frozen_string_literal: true
module Boards
module EpicBoards
class DestroyService < ::Boards::DestroyService
extend ::Gitlab::Utils::Override
override :boards
def boards
parent.epic_boards
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Mutations::Boards::EpicBoards::Destroy do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
let_it_be_with_reload(:board) { create(:epic_board, group: group) }
let_it_be(:another_board) { create(:epic_board, group: group) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
subject { mutation.resolve(id: board.to_global_id) }
context 'field tests' do
subject { described_class }
it { is_expected.to have_graphql_arguments(:id) }
it { is_expected.to have_graphql_fields(:epic_board).at_least }
end
before do
stub_licensed_features(epics: true)
end
it 'raises error when user does not have permission to destroy the board' do
expect { subject }
.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when user has permission to destroy the board' do
before do
group.add_reporter(current_user)
end
it 'destroys the epic board' do
result = mutation.resolve(id: board.to_global_id)
expect(result[:errors]).to be_empty
expect(result[:epic_board]).to be_nil
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Boards::EpicBoards::Destroy do
include GraphqlHelpers
let_it_be_with_reload(:current_user) { create(:user) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be(:board) { create(:epic_board, group: group) }
let_it_be(:other_board) { create(:epic_board, group: group) }
let(:mutation) do
variables = {
id: board.to_global_id.to_s
}
graphql_mutation(:destroy_epic_board, variables)
end
subject { post_graphql_mutation(mutation, current_user: current_user) }
def mutation_response
graphql_mutation_response(:destroy_epic_board)
end
before do
stub_licensed_features(epics: true)
end
context 'when the user does not have permission' do
it_behaves_like 'a mutation that returns a top-level access error'
it 'does not destroy the board' do
expect { subject }.not_to change { ::Boards::EpicBoard.count }
end
end
context 'when the user has permission' do
before do
group.add_developer(current_user)
end
context 'when everything is ok' do
it 'destroys the board' do
expect { subject }.to change { ::Boards::EpicBoard.count }.by(-1)
end
it 'returns an empty board' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response).to have_key('epicBoard')
expect(mutation_response['epicBoard']).to be_nil
end
end
context 'when there is only 1 board for the parent' do
before do
other_board.destroy!
end
it 'does not destroy the board' do
expect { subject }.not_to change { ::Boards::EpicBoard.count }
end
it 'returns an error and not nil board' do
subject
expect(mutation_response['errors']).not_to be_empty
expect(mutation_response['epicBoard']).not_to be_nil
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Boards::DestroyService do
describe '#execute' do
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
shared_examples 'remove the board' do |parent_name|
let(:parent) { public_send(parent_name) }
let!(:board) { create(:board, parent_name => parent) }
subject(:service) { described_class.new(parent, double) }
context "when #{parent_name} have more than one board" do
it "removes board from #{parent_name}" do
create(:board, parent_name => parent)
expect do
expect(service.execute(board)).to be_success
end.to change(parent.boards, :count).by(-1)
end
end
context "when #{parent_name} have one board" do
it "does not remove board from #{parent_name}" do
expect do
expect(service.execute(board)).to be_error
end.not_to change(parent.boards, :count)
end
end
end
it_behaves_like 'remove the board', :group
it_behaves_like 'remove the board', :project
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Boards::EpicBoards::DestroyService do
let_it_be(:parent) { create(:group) }
let(:boards) { parent.epic_boards }
let(:board_factory) { :epic_board }
it_behaves_like 'board destroy service'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Boards::DestroyService do
context 'with project board' do
let_it_be(:parent) { create(:project) }
let(:boards) { parent.boards }
let(:board_factory) { :board }
it_behaves_like 'board destroy service'
end
context 'with group board' do
let_it_be(:parent) { create(:group) }
let(:boards) { parent.boards }
let(:board_factory) { :board }
it_behaves_like 'board destroy service'
end
end
# frozen_string_literal: true
RSpec.shared_examples 'board destroy service' do
describe '#execute' do
let(:parent_type) { parent.is_a?(Project) ? :project : :group }
let!(:board) { create(board_factory, parent_type => parent) }
subject(:service) { described_class.new(parent, double) }
context 'when there is more than one board' do
let!(:board2) { create(board_factory, parent_type => parent) }
it 'destroys the board' do
create(board_factory, parent_type => parent)
expect do
expect(service.execute(board)).to be_success
end.to change(boards, :count).by(-1)
end
end
context 'when there is only one board' do
it 'does not remove board' do
expect do
expect(service.execute(board)).to be_error
end.not_to change(boards, :count)
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