Commit a077194f authored by Jan Provaznik's avatar Jan Provaznik

Allow to create epic from ancestor board

When an epic is created from an epic board, user can
can select a group where the epic will be created. This group
can be either the same group as board's group or any subgroup.

For this reason when we are searching for the board, it can exist in
any ancestor group.

Changelog: added
EE: true
parent 31462b14
...@@ -10,7 +10,7 @@ module Boards ...@@ -10,7 +10,7 @@ module Boards
end end
def execute def execute
relation = group.epic_boards relation = init_relation
relation = by_id(relation) relation = by_id(relation)
relation.order_by_name_asc relation.order_by_name_asc
...@@ -18,6 +18,12 @@ module Boards ...@@ -18,6 +18,12 @@ module Boards
private private
def init_relation
return group.epic_boards unless params[:include_ancestor_groups]
::Boards::EpicBoard.for_groups(group.self_and_ancestors)
end
def by_id(relation) def by_id(relation)
return relation unless params[:id].present? return relation unless params[:id].present?
......
...@@ -12,6 +12,7 @@ module Boards ...@@ -12,6 +12,7 @@ module Boards
validates :name, length: { maximum: 255 }, presence: true validates :name, length: { maximum: 255 }, presence: true
scope :order_by_name_asc, -> { order(arel_table[:name].lower.asc).order(id: :asc) } scope :order_by_name_asc, -> { order(arel_table[:name].lower.asc).order(id: :asc) }
scope :for_groups, ->(ids) { where(group_id: ids) }
def lists def lists
epic_lists epic_lists
......
...@@ -34,7 +34,10 @@ module Boards ...@@ -34,7 +34,10 @@ module Boards
end end
def board def board
@board ||= parent.epic_boards.find(params.delete(:board_id)) @board ||= Boards::EpicBoardsFinder
.new(group, include_ancestor_groups: true, id: params.delete(:board_id))
.execute
.first
end end
def list def list
...@@ -50,10 +53,8 @@ module Boards ...@@ -50,10 +53,8 @@ module Boards
end end
def check_arguments def check_arguments
begin unless board && Ability.allowed?(current_user, :read_epic_board, board)
board return 'Board not found'
rescue ActiveRecord::RecordNotFound
return 'Board not found' if @board.blank?
end end
begin begin
......
...@@ -4,11 +4,13 @@ require 'spec_helper' ...@@ -4,11 +4,13 @@ require 'spec_helper'
RSpec.describe Boards::EpicBoardsFinder do RSpec.describe Boards::EpicBoardsFinder do
describe '#execute' do describe '#execute' do
let_it_be(:group) { create(:group) } let_it_be(:parent_group) { create(:group) }
let_it_be(:group) { create(:group, parent: parent_group) }
let_it_be(:epic_board1) { create(:epic_board, name: 'Acd', group: group) } let_it_be(:epic_board1) { create(:epic_board, name: 'Acd', group: group) }
let_it_be(:epic_board2) { create(:epic_board, name: 'abd', group: group) } let_it_be(:epic_board2) { create(:epic_board, name: 'abd', group: group) }
let_it_be(:epic_board3) { create(:epic_board, name: 'Bbd', group: group) } let_it_be(:epic_board3) { create(:epic_board, name: 'Bbd', group: group) }
let_it_be(:epic_board4) { create(:epic_board) } let_it_be(:epic_board4) { create(:epic_board) }
let_it_be(:epic_board5) { create(:epic_board, name: 'foo', group: parent_group) }
let(:params) { {} } let(:params) { {} }
...@@ -18,6 +20,14 @@ RSpec.describe Boards::EpicBoardsFinder do ...@@ -18,6 +20,14 @@ RSpec.describe Boards::EpicBoardsFinder do
expect(result).to eq([epic_board2, epic_board1, epic_board3]) expect(result).to eq([epic_board2, epic_board1, epic_board3])
end end
context 'when include_ancestor_groups parameter is set' do
let(:params) { { include_ancestor_groups: true } }
it 'finds all epic boards in the group or ancestor groups' do
expect(result).to eq([epic_board2, epic_board1, epic_board3, epic_board5])
end
end
context 'when ID parameter is set' do context 'when ID parameter is set' do
let(:params) { { id: epic_board2.id } } let(:params) { { id: epic_board2.id } }
......
...@@ -15,13 +15,21 @@ RSpec.describe Boards::EpicBoard do ...@@ -15,13 +15,21 @@ RSpec.describe Boards::EpicBoard do
it { is_expected.to validate_length_of(:name).is_at_most(255) } it { is_expected.to validate_length_of(:name).is_at_most(255) }
end end
describe '.order_by_name_asc' do describe 'scopes' do
let_it_be(:board1) { create(:epic_board, name: 'B') } let_it_be(:board1) { create(:epic_board, name: 'B') }
let_it_be(:board2) { create(:epic_board, name: 'a') } let_it_be(:board2) { create(:epic_board, name: 'a') }
let_it_be(:board3) { create(:epic_board, name: 'A') } let_it_be(:board3) { create(:epic_board, name: 'A') }
it 'returns in case-insensitive alphabetical order and then by ascending ID' do describe '.order_by_name_asc' do
expect(described_class.order_by_name_asc).to eq [board2, board3, board1] it 'returns in case-insensitive alphabetical order and then by ascending ID' do
expect(described_class.order_by_name_asc).to eq [board2, board3, board1]
end
end
describe '.for_groups' do
it 'returns boards only in selected groups' do
expect(described_class.for_groups([board1.group_id, board2.group_id])).to match_array([board1, board2])
end
end end
end end
end end
...@@ -4,7 +4,8 @@ require 'spec_helper' ...@@ -4,7 +4,8 @@ require 'spec_helper'
RSpec.describe Boards::Epics::CreateService do RSpec.describe Boards::Epics::CreateService do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be(:parent_group) { create(:group) }
let_it_be(:group) { create(:group, parent: parent_group) }
let_it_be(:board) { create(:epic_board, group: group) } let_it_be(:board) { create(:epic_board, group: group) }
describe '#execute' do describe '#execute' do
...@@ -19,34 +20,41 @@ RSpec.describe Boards::Epics::CreateService do ...@@ -19,34 +20,41 @@ RSpec.describe Boards::Epics::CreateService do
let(:params) { valid_params } let(:params) { valid_params }
subject do subject(:response) do
described_class.new(group, user, params).execute described_class.new(group, user, params).execute
end end
shared_examples 'epic creation error' do |error_pattern| shared_examples 'epic creation error' do |error_pattern|
it 'does not create epic' do it 'does not create epic' do
response = subject
expect(response).to be_error expect(response).to be_error
expect(response.message).to match(error_pattern) expect(response.message).to match(error_pattern)
end end
end end
shared_examples 'success epic creation' do
it 'creates an epic' do
expect(response).to be_success
expect(response.payload).to be_a(Epic)
end
specify { expect { subject }.to change { Epic.count }.by(1) }
end
context 'when epics feature is available' do context 'when epics feature is available' do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
group.add_developer(user) group.add_developer(user)
end end
context 'when arguments are valid' do it_behaves_like 'success epic creation'
it 'creates an epic' do
response = subject
expect(response).to be_success context 'when board is in an ancestor group' do
expect(response.payload).to be_a(Epic) let_it_be(:parent_board) { create(:epic_board, group: parent_group) }
end let_it_be(:parent_list) { create(:epic_list, epic_board: parent_board) }
let(:params) { valid_params.merge(board_id: parent_board.id, list_id: parent_list.id) }
specify { expect { subject }.to change { Epic.count }.by(1) } it_behaves_like 'success epic creation'
end end
context 'when arguments are not valid' do context 'when arguments are not valid' 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