Commit ad96ac9d authored by Felipe Artur's avatar Felipe Artur

EE PORT Bring one group issue board to CE

parent 50c66197
module Boards
class IssuesController < Boards::ApplicationController
prepend EE::BoardsResponses
prepend EE::Boards::IssuesController
include BoardsResponses
include ControllerWithCrossProjectAccessCheck
requires_cross_project_access if: -> { board&.group_board? }
before_action :whitelist_query_limiting, only: [:index, :update]
before_action :authorize_read_issue, only: [:index]
before_action :authorize_create_issue, only: [:create]
before_action :authorize_update_issue, only: [:update]
skip_before_action :authenticate_user!, only: [:index]
def index
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
......@@ -66,11 +66,21 @@ module Boards
end
def issues_finder
IssuesFinder.new(current_user, project_id: board_parent.id)
if board.group_board?
IssuesFinder.new(current_user, group_id: board_parent.id)
else
IssuesFinder.new(current_user, project_id: board_parent.id)
end
end
def project
board_parent
@project ||= begin
if board.group_board?
Project.find(issue_params[:project_id])
else
board_parent
end
end
end
def move_params
......
module Boards
class ListsController < Boards::ApplicationController
prepend EE::BoardsResponses
include BoardsResponses
before_action :authorize_admin_list, only: [:create, :update, :destroy, :generate]
......
module BoardsResponses
include Gitlab::Utils::StrongMemoize
def board_params
params.require(:board).permit(:name, :weight, :milestone_id, :assignee_id, label_ids: [])
end
def parent
strong_memoize(:parent) do
group? ? group : project
end
end
def boards_path
if group?
group_boards_path(parent)
else
project_boards_path(parent)
end
end
def board_path(board)
if group?
group_board_path(parent, board)
else
project_board_path(parent, board)
end
end
def group?
instance_variable_defined?(:@group)
end
def authorize_read_list
authorize_action_for!(board.parent, :read_list)
ability = board.group_board? ? :read_group : :read_list
authorize_action_for!(board.parent, ability)
end
def authorize_read_issue
authorize_action_for!(board.parent, :read_issue)
ability = board.group_board? ? :read_group : :read_issue
authorize_action_for!(board.parent, ability)
end
def authorize_update_issue
......@@ -31,6 +67,10 @@ module BoardsResponses
respond_with(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def serialize_as_json(resource)
resource.as_json(only: [:id])
end
def respond_with(resource)
respond_to do |format|
format.html
......
class Groups::BoardsController < Groups::ApplicationController
prepend EE::Boards::BoardsController
prepend EE::BoardsResponses
include BoardsResponses
before_action :check_group_issue_boards_available!
before_action :assign_endpoint_vars
def index
......@@ -23,4 +21,8 @@ class Groups::BoardsController < Groups::ApplicationController
@namespace_path = group.to_param
@labels_endpoint = group_labels_url(group)
end
def serialize_as_json(resource)
resource.as_json(only: [:id])
end
end
class Projects::BoardsController < Projects::ApplicationController
prepend EE::Boards::BoardsController
prepend EE::BoardsResponses
include BoardsResponses
include IssuableCollections
......
......@@ -19,23 +19,37 @@ module BoardsHelper
end
def build_issue_link_base
project_issues_path(@project)
if board.group_board?
"#{group_path(@board.group)}/:project_path/issues"
else
project_issues_path(@project)
end
end
def board_base_url
project_boards_path(@project)
if board.group_board?
group_boards_url(@group)
else
project_boards_path(@project)
end
end
def multiple_boards_available?
current_board_parent.multiple_issue_boards_available?(current_user)
current_board_parent.multiple_issue_boards_available?
end
def current_board_path(board)
@current_board_path ||= project_board_path(current_board_parent, board)
@current_board_path ||= begin
if board.group_board?
group_board_path(current_board_parent, board)
else
project_board_path(current_board_parent, board)
end
end
end
def current_board_parent
@current_board_parent ||= @project
@current_board_parent ||= @group || @project
end
def can_admin_issue?
......@@ -49,7 +63,8 @@ module BoardsHelper
labels: labels_filter_path(true),
labels_endpoint: @labels_endpoint,
namespace_path: @namespace_path,
project_path: @project&.try(:path)
project_path: @project&.try(:path),
group_path: @group&.path
}
end
......@@ -62,6 +77,7 @@ module BoardsHelper
first_user: current_user&.username,
current_user: 'true',
project_id: @project&.try(:id),
group_id: @group&.id,
null_user: 'true',
multi_select: 'true',
'dropdown-header': dropdown_options[:data][:'dropdown-header'],
......
......@@ -137,7 +137,7 @@ module GroupsHelper
links = [:overview, :group_members]
if can?(current_user, :read_cross_project)
links += [:activity, :issues, :labels, :milestones, :merge_requests]
links += [:activity, :issues, :boards, :labels, :milestones, :merge_requests]
end
if can?(current_user, :admin_group, @group)
......
class Board < ActiveRecord::Base
prepend EE::Board
belongs_to :group
belongs_to :project
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
validates :project, presence: true, if: :project_needed?
validates :group, presence: true, unless: :project
def project_needed?
true
!group
end
def parent
project
@parent ||= group || project
end
def group_board?
false
group_id.present?
end
def backlog_list
......
......@@ -36,6 +36,8 @@ class Group < Namespace
has_many :hooks, dependent: :destroy, class_name: 'GroupHook' # rubocop:disable Cop/ActiveRecordDependent
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :boards
# We cannot simply set `has_many :audit_events, as: :entity, dependent: :destroy`
# here since Group inherits from Namespace, the entity_type would be set to `Namespace`.
has_many :audit_events, -> { where(entity_type: Group) }, foreign_key: 'entity_id'
......
class Label < ActiveRecord::Base
# EE specific
prepend EE::Label
include CacheMarkdownField
include Referable
include Subscribable
......@@ -38,6 +35,7 @@ class Label < ActiveRecord::Base
scope :templates, -> { where(template: true) }
scope :with_title, ->(title) { where(title: title) }
scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
scope :on_group_boards, ->(group_id) { with_lists_and_board.where(boards: { group_id: group_id }) }
scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
def self.prioritized(project)
......
......@@ -232,9 +232,9 @@ class Namespace < ActiveRecord::Base
has_parent?
end
## EE only
def multiple_issue_boards_available?(user = nil)
feature_available?(:multiple_issue_boards)
# Overriden on EE module
def multiple_issue_boards_available?
false
end
def full_path_was
......
......@@ -1705,8 +1705,9 @@ class Project < ActiveRecord::Base
end
end
def multiple_issue_boards_available?(user)
feature_available?(:multiple_issue_boards, user)
# Overridden on EE module
def multiple_issue_boards_available?
false
end
def full_path_was
......
......@@ -50,6 +50,14 @@ class GroupPolicy < BasePolicy
rule { has_access }.enable :read_namespace
rule { developer }.enable :admin_milestones
rule { reporter }.policy do
enable :admin_label
enable :admin_list
enable :admin_issue
end
rule { developer }.enable :admin_milestones
rule { reporter }.enable :admin_label
......
......@@ -41,7 +41,11 @@ module Boards
end
def set_parent
params[:project_id] = parent.id
if parent.is_a?(Group)
params[:group_id] = parent.id
else
params[:project_id] = parent.id
end
end
def set_state
......
module Boards
module Issues
class MoveService < Boards::BaseService
prepend EE::Boards::Issues::MoveService
def execute(issue)
return false unless can?(current_user, :update_issue, issue)
return false if issue_params.empty?
......@@ -62,8 +60,10 @@ module Boards
label_ids =
if moving_to_list.movable?
moving_from_list.label_id
elsif board.group_board?
::Label.on_group_boards(parent.id).pluck(:label_id)
else
Label.on_project_boards(parent.id).pluck(:label_id)
::Label.on_project_boards(parent.id).pluck(:label_id)
end
Array(label_ids).compact
......
module Boards
module Lists
class CreateService < Boards::BaseService
prepend EE::Boards::Lists::CreateService
def execute(board)
List.transaction do
label = available_labels_for(board).find(params[:label_id])
......@@ -14,7 +12,11 @@ module Boards
private
def available_labels_for(board)
LabelsFinder.new(current_user, project_id: parent.id).execute
if board.group_board?
parent.labels
else
LabelsFinder.new(current_user, project_id: parent.id).execute
end
end
def next_position(board)
......
- issues_count = group_issues_count(state: 'opened')
- merge_requests_count = group_merge_requests_count(state: 'opened')
- issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index']
- if @group.feature_available?(:group_issue_boards)
- issues_sub_menu_items.push('boards#index', 'boards#show')
- issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index', 'boards#index', 'boards#show']
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
......
......@@ -80,7 +80,7 @@ constraints(GroupUrlConstrainer.new) do
end
resources :billings, only: [:index]
resources :boards, only: [:index, :show, :create, :update, :destroy]
resources :epics do
member do
get :realtime_changes
......@@ -89,6 +89,9 @@ constraints(GroupUrlConstrainer.new) do
resources :epic_issues, only: [:index, :create, :destroy, :update], as: 'issues', path: 'issues'
end
# On CE only index and show are needed
resources :boards, only: [:index, :show, :create, :update, :destroy]
legacy_ee_group_boards_redirect = redirect do |params, request|
path = "/groups/#{params[:group_id]}/-/boards"
path << "/#{params[:extra_params]}" if params[:extra_params].present?
......
......@@ -422,6 +422,7 @@ constraints(ProjectUrlConstrainer.new) do
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
# On CE only index and show are needed
resources :boards, only: [:index, :show, :create, :update, :destroy]
resources :todos, only: [:create]
......
# Shared actions between Groups::BoardsController and Projects::BoardsController
module EE
module Boards
module BoardsController
include ::Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
prepended do
before_action :check_multiple_issue_boards_available!, only: [:create]
before_action :authorize_create_board!, only: [:create]
before_action :authorize_admin_board!, only: [:create, :update, :destroy]
before_action :find_board, only: [:update, :destroy]
end
def create
......@@ -24,28 +25,26 @@ module EE
end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def update
service = ::Boards::UpdateService.new(parent, current_user, board_params)
service.execute(@board)
service.execute(board)
respond_to do |format|
format.json do
if @board.valid?
extra_json = { board_path: board_path(@board) }
render json: serialize_as_json(@board).merge(extra_json)
if board.valid?
extra_json = { board_path: board_path(board) }
render json: serialize_as_json(board).merge(extra_json)
else
render json: @board.errors, status: :unprocessable_entity
render json: board.errors, status: :unprocessable_entity
end
end
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def destroy
service = ::Boards::DestroyService.new(parent, current_user)
service.execute(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables
service.execute(board)
respond_to do |format|
format.json { head :ok }
......@@ -55,36 +54,22 @@ module EE
private
def authorize_admin_board!
return render_404 unless can?(current_user, :admin_board, parent)
end
def board_params
params.require(:board).permit(:name, :weight, :milestone_id, :assignee_id, label_ids: [])
end
def find_board
@board = parent.boards.find(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def parent
@parent ||= @project || @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
def board
strong_memoize(:board) do
parent.boards.find(params[:id])
end
end
def boards_path
if @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
group_boards_path(parent)
def authorize_create_board!
if group?
check_multiple_group_issue_boards_available!
else
project_boards_path(parent)
check_multiple_project_issue_boards_available!
end
end
def board_path(board)
if @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
group_board_path(parent, board)
else
project_board_path(parent, board)
end
def authorize_admin_board!
return render_404 unless can?(current_user, :admin_board, parent)
end
def serialize_as_json(resource)
......
module EE
module Boards
module IssuesController
extend ActiveSupport::Concern
include ControllerWithCrossProjectAccessCheck
prepended do
requires_cross_project_access if: -> { board.group_board? }
end
def issues_finder
return super unless board.group_board?
::IssuesFinder.new(current_user, group_id: board_parent.id)
end
def project
@project ||= begin
if board.group_board?
::Project.find(issue_params[:project_id])
else
super
end
end
end
end
end
end
module EE
module BoardsResponses
# Shared authorizations between projects and groups which
# have different policies on EE.
def authorize_read_list
ability = board.group_board? ? :read_group : :read_list
authorize_action_for!(board.parent, ability)
end
def authorize_read_issue
ability = board.group_board? ? :read_group : :read_issue
authorize_action_for!(board.parent, ability)
end
end
end
......@@ -6,7 +6,7 @@ module EE
def board_data
show_feature_promotion = (@project && show_promotions? &&
(!@project.feature_available?(:multiple_issue_boards) ||
(!@project.feature_available?(:multiple_project_issue_boards) ||
!@project.feature_available?(:scoped_issue_board) ||
!@project.feature_available?(:issue_board_focus_mode)))
......@@ -24,12 +24,6 @@ module EE
super.merge(data)
end
def build_issue_link_base
return super unless @board.group_board?
"#{group_path(@board.group)}/:project_path/issues"
end
def current_board_json
board = @board || @boards.first
......@@ -43,42 +37,8 @@ module EE
)
end
def board_base_url
if board.group_board?
group_boards_url(@group)
else
super
end
end
def current_board_path(board)
@current_board_path ||= begin
if board.group_board?
group_board_path(current_board_parent, board)
else
super(board)
end
end
end
def current_board_parent
@current_board_parent ||= @group || super
end
def can_admin_issue?
can?(current_user, :admin_issue, current_board_parent)
end
def board_list_data
super.merge(group_path: @group&.path)
end
def board_sidebar_user_data
super.merge(group_id: @group&.id)
end
def boards_link_text
if @project.multiple_issue_boards_available?(current_user)
if @project.multiple_issue_boards_available?
s_("IssueBoards|Boards")
else
s_("IssueBoards|Board")
......
......@@ -6,7 +6,6 @@ module EE
EMPTY_SCOPE_STATE = [nil, -1].freeze
prepended do
belongs_to :group
belongs_to :milestone
has_many :board_labels
......@@ -20,19 +19,6 @@ module EE
has_many :labels, through: :board_labels
validates :name, presence: true
validates :group, presence: true, unless: :project
end
def project_needed?
!group
end
def parent
@parent ||= group || project
end
def group_board?
group_id.present?
end
def milestone
......
......@@ -7,7 +7,6 @@ module EE
extend ActiveSupport::Concern
included do
has_many :boards
has_many :epics
state_machine :ldap_sync_status, namespace: :ldap_sync, initial: :ready do
......
module EE
module Label
extend ActiveSupport::Concern
prepended do
scope :on_group_boards, ->(group_id) { with_lists_and_board.where(boards: { group_id: group_id }) }
end
end
end
......@@ -160,6 +160,11 @@ module EE
actual_plan&.pipeline_size_limit.to_i
end
override :multiple_issue_boards_available?
def multiple_issue_boards_available?
feature_available?(:multiple_group_issue_boards)
end
private
def validate_plan_name
......
......@@ -223,6 +223,11 @@ module EE
end
end
override :multiple_issue_boards_available?
def multiple_issue_boards_available?
feature_available?(:multiple_project_issue_boards)
end
def service_desk_enabled
::EE::Gitlab::ServiceDesk.enabled?(project: self) && super
end
......
......@@ -23,7 +23,7 @@ class License < ActiveRecord::Base
merge_request_squash
multiple_ldap_servers
multiple_issue_assignees
multiple_issue_boards
multiple_project_issue_boards
push_rules
protected_refs_for_users
related_issues
......@@ -42,10 +42,10 @@ class License < ActiveRecord::Base
external_files_in_gitlab_ci
file_locks
geo
group_issue_boards
jira_dev_panel_integration
ldap_group_sync_filter
multiple_clusters
multiple_group_issue_boards
merge_request_performance_metrics
object_storage
service_desk
......@@ -86,7 +86,8 @@ class License < ActiveRecord::Base
merge_request_approvers
merge_request_squash
multiple_issue_assignees
multiple_issue_boards
multiple_project_issue_boards
multiple_group_issue_boards
protected_refs_for_users
push_rules
related_issues
......
......@@ -20,7 +20,6 @@ module EE
rule { reporter }.policy do
enable :admin_list
enable :admin_board
enable :admin_issue
end
condition(:can_owners_manage_ldap, scope: :global) do
......
......@@ -5,7 +5,7 @@ module EE
override :can_create_board?
def can_create_board?
parent.feature_available?(:multiple_issue_boards) || super
parent.multiple_issue_boards_available? || super
end
end
end
......
......@@ -2,14 +2,6 @@ module EE
module Boards
module Issues
module ListService
def set_parent
if parent.is_a?(Group)
params[:group_id] = parent.id
else
super
end
end
def issues_label_links
if has_valid_milestone?
super.where("issues.milestone_id = ?", board.milestone_id)
......
......@@ -5,7 +5,7 @@ module EE
override :execute
def execute
if parent.multiple_issue_boards_available?(current_user)
if parent.multiple_issue_boards_available?
super
else
super.limit(1)
......
module EE
module Boards
module Lists
module CreateService
def available_labels_for(board)
if board.group_board?
parent.labels
else
super
end
end
end
end
end
end
module EE
module Boards
module MoveService
def remove_label_ids
label_ids =
if moving_to_list.movable?
moving_from_list.label_id
elsif board.group_board?
::Label.on_group_boards(parent.id).pluck(:label_id)
else
::Label.on_project_boards(parent.id).pluck(:label_id)
end
Array(label_ids).compact
end
end
end
end
......@@ -28,7 +28,7 @@
- if can?(current_user, :admin_board, parent)
.dropdown-footer
%ul.dropdown-footer-list
- if parent.feature_available?(:multiple_issue_boards)
- if parent.multiple_issue_boards_available?
%li
%a{ "href" => "#", "v-on:click.prevent" => "showPage('new')" }
Create new board
......
......@@ -9,7 +9,7 @@
- else
= _('Improve Issue boards with GitLab Enterprise Edition.')
%ul
- unless @project.feature_available?(:multiple_issue_boards)
- unless @project.multiple_issue_boards_available?
%li
= link_to _('Multiple issue boards'), help_page_path('user/project/issue_board.html', anchor:'use-cases-for-multiple-issue-boards'), target: '_blank'
- unless @project.feature_available?(:scoped_issue_board)
......
......@@ -6,7 +6,7 @@ module EE
included do
helpers do
def create_board
forbidden! unless ::License.feature_available?(:multiple_issue_boards)
forbidden! unless board_parent.multiple_issue_boards_available?
board =
::Boards::CreateService.new(board_parent, current_user, { name: params[:name] }).execute
......@@ -15,7 +15,7 @@ module EE
end
def delete_board
forbidden! unless ::License.feature_available?(:multiple_issue_boards)
forbidden! unless board_parent.multiple_issue_boards_available?
destroy_conditionally!(board) do |board|
service = ::Boards::DestroyService.new(board_parent, current_user)
......
module EE
module API
class GroupBoards < ::Grape::API
include ::API::PaginationParams
include ::API::BoardsResponses
include BoardsResponses
before do
authenticate!
end
helpers do
def board_parent
user_group
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: ::API::API::PROJECT_ENDPOINT_REQUIREMENTS do
segment ':id/boards' do
desc 'Create a group board' do
detail 'This feature was introduced in 10.4'
success ::API::Entities::Board
end
params do
requires :name, type: String, desc: 'The board name'
end
post '/' do
authorize!(:admin_board, board_parent)
create_board
end
desc 'Delete a group board' do
detail 'This feature was introduced in 10.4'
success ::API::Entities::Board
end
delete '/:board_id' do
authorize!(:admin_board, board_parent)
delete_board
end
end
end
end
end
end
......@@ -8,7 +8,7 @@ describe Groups::BoardsController do
allow(Ability).to receive(:allowed?).and_call_original
group.add_master(user)
sign_in(user)
stub_licensed_features(group_issue_boards: true)
stub_licensed_features(multiple_group_issue_boards: true)
end
describe 'GET index' do
......@@ -16,28 +16,6 @@ describe Groups::BoardsController do
expect { list_boards }.to change(group.boards, :count).by(1)
end
context 'when format is HTML' do
it 'renders template' do
list_boards
expect(response).to render_template :index
expect(response.content_type).to eq 'text/html'
end
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
end
it 'returns a not found 404 response' do
list_boards
expect(response).to have_gitlab_http_status(404)
expect(response.content_type).to eq 'text/html'
end
end
end
context 'when format is JSON' do
it 'returns a list of group boards' do
create(:board, group: group, milestone: create(:milestone, group: group))
......@@ -73,71 +51,4 @@ describe Groups::BoardsController do
get :index, group_id: group, format: format
end
end
describe 'GET show' do
let!(:board) { create(:board, group: group) }
context 'when format is HTML' do
it 'renders template' do
read_board board: board
expect(response).to render_template :show
expect(response.content_type).to eq 'text/html'
end
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
end
it 'returns a not found 404 response' do
read_board board: board
expect(response).to have_gitlab_http_status(404)
expect(response.content_type).to eq 'text/html'
end
end
end
context 'when format is JSON' do
it 'returns project board' do
read_board board: board, format: :json
expect(response).to match_response_schema('board')
end
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
end
it 'returns a not found 404 response' do
read_board board: board, format: :json
expect(response).to have_gitlab_http_status(404)
expect(response.content_type).to eq 'application/json'
end
end
end
context 'when board does not belong to group' do
it 'returns a not found 404 response' do
another_board = create(:board)
read_board board: another_board
expect(response).to have_gitlab_http_status(404)
end
end
it_behaves_like 'disabled when using an external authorization service' do
subject { read_board board: board }
end
def read_board(board:, format: :html)
get :show, group_id: group,
id: board.to_param,
format: format
end
end
end
......@@ -8,10 +8,6 @@ describe 'Multiple Issue Boards', :js do
let!(:board2) { create(:board, project: project) }
context 'with multiple issue boards enabled' do
before do
stub_licensed_features(multiple_issue_boards: true)
end
context 'authorized user' do
before do
project.add_master(user)
......@@ -150,7 +146,7 @@ describe 'Multiple Issue Boards', :js do
context 'with multiple issue boards disabled' do
before do
stub_licensed_features(multiple_issue_boards: false)
stub_licensed_features(multiple_project_issue_boards: false)
project.add_master(user)
login_as(user)
......
......@@ -23,7 +23,6 @@ describe 'Scoped issue boards', :js do
before do
allow_any_instance_of(ApplicationHelper).to receive(:collapsed_sidebar?).and_return(true)
stub_licensed_features(multiple_issue_boards: true)
stub_licensed_features(scoped_issue_boards: true)
end
......@@ -86,7 +85,7 @@ describe 'Scoped issue boards', :js do
end
it 'only shows group labels in list on group boards' do
stub_licensed_features(group_issue_boards: true)
stub_licensed_features(multiple_group_issue_boards: true)
visit group_boards_path(group)
wait_for_requests
......
require 'spec_helper'
describe API::Boards do
set(:user) { create(:user) }
set(:user) { create(:user) }
set(:board_parent) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) }
set(:milestone) { create(:milestone, project: board_parent) }
set(:board) { create(:board, project: board_parent, milestone: milestone) }
......
......@@ -8,7 +8,6 @@ describe API::GroupBoards do
set(:board_parent) { create(:group, :public) }
before do
stub_licensed_features(group_issue_boards: true)
board_parent.add_owner(user)
end
......@@ -37,7 +36,6 @@ describe API::GroupBoards do
set(:milestone) { create(:milestone, group: board_parent) }
set(:board_label) { create(:group_label, group: board_parent) }
# EE only
set(:board) do
create(:board, group: board_parent,
milestone: milestone,
......@@ -48,16 +46,4 @@ describe API::GroupBoards do
it_behaves_like 'group and project boards', "/groups/:id/boards", true
it_behaves_like 'multiple and scoped issue boards', "/groups/:id/boards"
describe 'POST /groups/:id/boards/lists' do
let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" }
it 'does not create lists for child project labels' do
project_label = create(:label, project: project)
post api(url, user), label_id: project_label.id
expect(response).to have_gitlab_http_status(400)
end
end
end
......@@ -18,7 +18,7 @@ describe Boards::CreateService, services: true do
shared_examples 'boards create service' do
context 'With the feature available' do
before do
stub_licensed_features(multiple_issue_boards: true)
stub_licensed_features(multiple_group_issue_boards: true)
end
context 'with valid params' do
......@@ -68,7 +68,7 @@ describe Boards::CreateService, services: true do
end
it 'skips creating a second board when the feature is not available' do
stub_licensed_features(multiple_issue_boards: false)
stub_licensed_features(multiple_project_issue_boards: false)
service = described_class.new(parent, double)
expect(service.execute).not_to be_nil
......
......@@ -39,16 +39,10 @@ describe Boards::Issues::ListService, services: true do
let!(:closed_issue4) { create(:labeled_issue, :closed, project: project1, labels: [p1]) }
let!(:closed_issue5) { create(:labeled_issue, :closed, project: project1, labels: [development]) }
before do
group.add_developer(user)
end
it 'delegates search to IssuesFinder' do
params = { board_id: board.id, id: list1.id }
expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original
let(:parent) { group }
described_class.new(group, user, params).execute
before do
parent.add_developer(user)
end
context 'when list_id is missing' do
......@@ -56,7 +50,7 @@ describe Boards::Issues::ListService, services: true do
it 'returns opened issues without board labels applied' do
params = { board_id: board.id }
issues = described_class.new(group, user, params).execute
issues = described_class.new(parent, user, params).execute
expect(issues).to match_array([opened_issue2, reopened_issue1, opened_issue1])
end
......@@ -67,7 +61,7 @@ describe Boards::Issues::ListService, services: true do
params = { board_id: board.id }
board.update_attribute(:milestone, m1)
issues = described_class.new(group, user, params).execute
issues = described_class.new(parent, user, params).execute
expect(issues).to match_array([opened_issue2, list1_issue2, reopened_issue1, opened_issue1])
end
......@@ -81,7 +75,7 @@ describe Boards::Issues::ListService, services: true do
end
it 'returns open issue for backlog without board label' do
issues = described_class.new(group, user, params).execute
issues = described_class.new(parent, user, params).execute
expect(issues).to match_array([opened_issue2, reopened_issue1, opened_issue1])
end
......@@ -93,7 +87,7 @@ describe Boards::Issues::ListService, services: true do
end
it 'returns open issue for backlog without board label' do
issues = described_class.new(group, user, params).execute
issues = described_class.new(parent, user, params).execute
expect(issues).to match_array([opened_issue2, reopened_issue1, opened_issue1])
end
......@@ -101,56 +95,5 @@ describe Boards::Issues::ListService, services: true do
end
end
end
context 'issues are ordered by priority' do
it 'returns opened issues when list_id is missing' do
params = { board_id: board.id }
issues = described_class.new(group, user, params).execute
expect(issues).to match_array([opened_issue2, reopened_issue1, opened_issue1])
end
it 'returns opened issues when listing issues from Backlog' do
params = { board_id: board.id, id: backlog.id }
issues = described_class.new(group, user, params).execute
expect(issues).to match_array([opened_issue2, reopened_issue1, opened_issue1])
end
it 'returns closed issues when listing issues from Closed' do
params = { board_id: board.id, id: closed.id }
issues = described_class.new(group, user, params).execute
expect(issues).to match_array([closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1])
end
it 'returns opened issues that have label list applied when listing issues from a label list' do
params = { board_id: board.id, id: list1.id }
issues = described_class.new(group, user, params).execute
expect(issues).to match_array([list1_issue3, list1_issue1, list1_issue2])
end
end
context 'with list that does not belong to the board' do
it 'raises an error' do
list = create(:list)
service = described_class.new(group, user, board_id: board.id, id: list.id)
expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with invalid list id' do
it 'raises an error' do
service = described_class.new(group, user, board_id: board.id, id: nil)
expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
require 'spec_helper'
describe Boards::Issues::MoveService, services: true do
describe '#execute' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:board1) { create(:board, group: group) }
let(:bug) { create(:group_label, group: group, name: 'Bug') }
let(:development) { create(:group_label, group: group, name: 'Development') }
let(:testing) { create(:group_label, group: group, name: 'Testing') }
let!(:list1) { create(:list, board: board1, label: development, position: 0) }
let!(:list2) { create(:list, board: board1, label: testing, position: 1) }
let!(:closed) { create(:closed_list, board: board1) }
before do
group.add_developer(user)
end
context 'when moving an issue between lists' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
it 'delegates the label changes to Issues::UpdateService' do
expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once
described_class.new(group, user, params).execute(issue)
end
it 'removes the label from the list it came from and adds the label of the list it goes to' do
described_class.new(group, user, params).execute(issue)
expect(issue.reload.labels).to contain_exactly(bug, testing)
end
end
context 'when moving to closed' do
let(:board2) { create(:board, group: group) }
let(:regression) { create(:group_label, group: group, name: 'Regression') }
let!(:list3) { create(:list, board: board2, label: regression, position: 1) }
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
it 'delegates the close proceedings to Issues::CloseService' do
expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
described_class.new(group, user, params).execute(issue)
end
it 'removes all list-labels from project boards and close the issue' do
described_class.new(group, user, params).execute(issue)
issue.reload
expect(issue.labels).to contain_exactly(bug)
expect(issue).to be_closed
end
end
context 'when moving from closed' do
let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
it 'delegates the re-open proceedings to Issues::ReopenService' do
expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
described_class.new(group, user, params).execute(issue)
end
it 'adds the label of the list it goes to and reopen the issue' do
described_class.new(group, user, params).execute(issue)
issue.reload
expect(issue.labels).to contain_exactly(bug, testing)
expect(issue.state).to eq("opened")
end
end
context 'when moving to same list' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
it 'returns false' do
expect(described_class.new(group, user, params).execute(issue)).to eq false
end
it 'keeps issues labels' do
described_class.new(group, user, params).execute(issue)
expect(issue.reload.labels).to contain_exactly(bug, development)
end
it 'sorts issues' do
[issue, issue1, issue2].each do |issue|
issue.move_to_end && issue.save!
end
params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
described_class.new(group, user, params).execute(issue)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
end
end
end
......@@ -8,14 +8,18 @@ describe Boards::ListService do
end
describe '#execute' do
it 'returns all issue boards when `multiple_issue_boards` is enabled' do
stub_licensed_features(multiple_issue_boards: true)
it 'returns all issue boards when multiple issue boards is enabled' do
if parent.is_a?(Group)
stub_licensed_features(multiple_group_issue_boards: true)
end
expect(service.execute.size).to eq(2)
end
it 'returns the first issue board when `multiple_issue_boards` is disabled' do
stub_licensed_features(multiple_issue_boards: false)
it 'returns the first issue board when multiple issue boards is disabled' do
if parent.is_a?(Project)
stub_licensed_features(multiple_project_issue_boards: false)
end
expect(service.execute.size).to eq(1)
end
......
require 'spec_helper'
describe Boards::Lists::CreateService, services: true do
describe '#execute' do
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
let(:user) { create(:user) }
let(:label) { create(:group_label, group: group, name: 'in-progress') }
subject(:service) { described_class.new(group, user, label_id: label.id) }
before do
group.add_developer(user)
end
context 'when board lists is empty' do
it 'creates a new list at beginning of the list' do
list = service.execute(board)
expect(list.position).to eq 0
end
end
context 'when board lists has the done list' do
it 'creates a new list at beginning of the list' do
list = service.execute(board)
expect(list.position).to eq 0
end
end
context 'when board lists has labels lists' do
it 'creates a new list at end of the lists' do
create(:list, board: board, position: 0)
create(:list, board: board, position: 1)
list = service.execute(board)
expect(list.position).to eq 2
end
end
context 'when board lists has label and done lists' do
it 'creates a new list at end of the label lists' do
list1 = create(:list, board: board, position: 0)
list2 = service.execute(board)
expect(list1.reload.position).to eq 0
expect(list2.reload.position).to eq 1
end
end
context 'when provided label does not belongs to the group' do
it 'raises an error' do
label = create(:label, name: 'in-development')
service = described_class.new(group, user, label_id: label.id)
expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
require 'spec_helper'
describe Boards::Lists::DestroyService, services: true do
describe '#execute' do
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
let(:user) { create(:user) }
context 'when list type is label' do
it 'removes list from board' do
list = create(:list, board: board)
service = described_class.new(group, user)
expect { service.execute(list) }.to change(board.lists, :count).by(-1)
end
it 'decrements position of higher lists' do
development = create(:list, board: board, position: 0)
review = create(:list, board: board, position: 1)
staging = create(:list, board: board, position: 2)
closed = board.closed_list
described_class.new(group, user).execute(development)
expect(review.reload.position).to eq 0
expect(staging.reload.position).to eq 1
expect(closed.reload.position).to be_nil
end
end
it 'does not remove list from board when list type is closed' do
list = board.closed_list
service = described_class.new(group, user)
expect { service.execute(list) }.not_to change(board.lists, :count)
end
end
end
require 'spec_helper'
describe Boards::Lists::ListService, services: true do
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
let(:label) { create(:group_label, group: group) }
let!(:list) { create(:list, board: board, label: label) }
let(:service) { described_class.new(group, double) }
describe '#execute' do
context 'when the board has a backlog list' do
let!(:backlog_list) { create(:backlog_list, board: board) }
it 'does not create a backlog list' do
expect { service.execute(board) }.not_to change(board.lists, :count)
end
it "returns board's lists" do
expect(service.execute(board)).to eq [backlog_list, list, board.closed_list]
end
end
context 'when the board does not have a backlog list' do
it 'creates a backlog list' do
expect { service.execute(board) }.to change(board.lists, :count).by(1)
end
it "returns board's lists" do
expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list]
end
end
end
end
require 'spec_helper'
describe Boards::Lists::MoveService, services: true do
describe '#execute' do
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
let(:user) { create(:user) }
let!(:planning) { create(:list, board: board, position: 0) }
let!(:development) { create(:list, board: board, position: 1) }
let!(:review) { create(:list, board: board, position: 2) }
let!(:staging) { create(:list, board: board, position: 3) }
let!(:closed) { create(:closed_list, board: board) }
context 'when list type is set to label' do
it 'keeps position of lists when new position is nil' do
service = described_class.new(group, user, position: nil)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is equal to old position' do
service = described_class.new(group, user, position: planning.position)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is negative' do
service = described_class.new(group, user, position: -1)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is equal to number of labels lists' do
service = described_class.new(group, user, position: board.lists.label.size)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is greater than number of labels lists' do
service = described_class.new(group, user, position: board.lists.label.size + 1)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'increments position of intermediate lists when new positon is equal to first position' do
service = described_class.new(group, user, position: 0)
service.execute(staging)
expect(current_list_positions).to eq [1, 2, 3, 0]
end
it 'decrements position of intermediate lists when new positon is equal to last position' do
service = described_class.new(group, user, position: board.lists.label.last.position)
service.execute(planning)
expect(current_list_positions).to eq [3, 0, 1, 2]
end
it 'decrements position of intermediate lists when new position is greater than old position' do
service = described_class.new(group, user, position: 2)
service.execute(planning)
expect(current_list_positions).to eq [2, 0, 1, 3]
end
it 'increments position of intermediate lists when new position is lower than old position' do
service = described_class.new(group, user, position: 1)
service.execute(staging)
expect(current_list_positions).to eq [0, 2, 3, 1]
end
end
it 'keeps position of lists when list type is closed' do
service = described_class.new(group, user, position: 2)
service.execute(closed)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
end
def current_list_positions
[planning, development, review, staging].map { |list| list.reload.position }
end
end
......@@ -4,7 +4,7 @@ shared_examples_for 'multiple and scoped issue boards' do |route_definition|
context 'multiple issue boards' do
before do
board_parent.add_reporter(user)
stub_licensed_features(multiple_issue_boards: true, group_issue_boards: true)
stub_licensed_features(multiple_group_issue_boards: true, multiple_project_issue_boards: true)
end
describe "POST #{route_definition}" do
......@@ -30,7 +30,7 @@ shared_examples_for 'multiple and scoped issue boards' do |route_definition|
context 'with the scoped_issue_board-feature available' do
it 'returns the milestone when the `scoped_issue_board` feature is enabled' do
stub_licensed_features(scoped_issue_board: true, group_issue_boards: true)
stub_licensed_features(scoped_issue_board: true)
get api(root_url, user)
......@@ -38,7 +38,7 @@ shared_examples_for 'multiple and scoped issue boards' do |route_definition|
end
it 'hides the milestone when the `scoped_issue_board` feature is disabled' do
stub_licensed_features(scoped_issue_board: false, group_issue_boards: true)
stub_licensed_features(scoped_issue_board: false)
get api(root_url, user)
......
......@@ -43,20 +43,24 @@ describe 'layouts/nav/sidebar/_group' do
end
describe 'group issue boards link' do
it 'is not visible when there is no valid license' do
stub_licensed_features(group_issue_boards: false)
context 'when multiple issue board is disabled' do
it 'shows link text in singular' do
render
render
expect(rendered).not_to have_text 'Boards'
expect(rendered).to have_text 'Board'
end
end
it 'is visible when there is valid license' do
stub_licensed_features(group_issue_boards: true)
context 'when multiple issue board is enabled' do
before do
stub_licensed_features(multiple_group_issue_boards: true)
end
render
it 'shows link text in plural' do
render
expect(rendered).to have_text 'Boards'
expect(rendered).to have_text 'Boards'
end
end
end
end
......
......@@ -132,6 +132,7 @@ module API
mount ::API::Features
mount ::API::Files
mount ::API::Groups
mount ::API::GroupBoards
mount ::API::GroupMilestones
mount ::API::Internal
mount ::API::Issues
......@@ -175,18 +176,19 @@ module API
mount ::API::Wikis
## EE-specific API V4 endpoints START
mount ::EE::API::Boards
mount ::EE::API::GroupBoards
mount ::API::EpicIssues
mount ::API::Epics
mount ::API::Geo
mount ::API::GeoNodes
mount ::API::GroupBoards
mount ::API::IssueLinks
mount ::API::Ldap
mount ::API::LdapGroupLinks
mount ::API::License
mount ::API::ProjectImport
mount ::API::ProjectPushRule
mount ::EE::API::Boards
## EE-specific API V4 endpoints END
route :any, '*path' do
......
......@@ -6,17 +6,12 @@ module API
before do
authenticate!
check_group_issue_boards!
end
helpers do
def board_parent
user_group
end
def check_group_issue_boards!
forbidden! unless ::License.feature_available?(:group_issue_boards)
end
end
params do
......@@ -25,47 +20,23 @@ module API
resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
segment ':id/boards' do
desc 'Get all group boards' do
detail 'This feature was introduced in 10.4'
success Entities::Board
end
params do
use :pagination
end
get '/' do
present paginate(board_parent.boards), with: Entities::Board
end
desc 'Find a group board' do
detail 'This feature was introduced in 10.4'
success Entities::Board
success ::API::Entities::Board
end
get '/:board_id' do
present board, with: Entities::Board
present board, with: ::API::Entities::Board
end
desc 'Create a group board' do
desc 'Get all group boards' do
detail 'This feature was introduced in 10.4'
success Entities::Board
end
params do
requires :name, type: String, desc: 'The board name'
end
post '/' do
authorize!(:admin_board, board_parent)
create_board
end
desc 'Delete a group board' do
detail 'This feature was introduced in 10.4'
success Entities::Board
use :pagination
end
delete '/:board_id' do
authorize!(:admin_board, board_parent)
delete_board
get '/' do
present paginate(board_parent.boards), with: Entities::Board
end
end
......
require 'spec_helper'
describe Groups::BoardsController do
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
group.add_master(user)
sign_in(user)
end
describe 'GET index' do
it 'creates a new board when group does not have one' do
expect { list_boards }.to change(group.boards, :count).by(1)
end
context 'when format is HTML' do
it 'renders template' do
list_boards
expect(response).to render_template :index
expect(response.content_type).to eq 'text/html'
end
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
end
it 'returns a not found 404 response' do
list_boards
expect(response).to have_gitlab_http_status(404)
expect(response.content_type).to eq 'text/html'
end
end
end
context 'when format is JSON' do
it 'return an array with one group board' do
create(:board, group: group, milestone: create(:milestone, group: group))
list_boards format: :json
parsed_response = JSON.parse(response.body)
expect(response).to match_response_schema('boards')
expect(parsed_response.length).to eq 1
end
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
end
it 'returns a not found 404 response' do
list_boards format: :json
expect(response).to have_gitlab_http_status(404)
expect(response.content_type).to eq 'application/json'
end
end
end
def list_boards(format: :html)
get :index, group_id: group, format: format
end
end
describe 'GET show' do
let!(:board) { create(:board, group: group) }
context 'when format is HTML' do
it 'renders template' do
read_board board: board
expect(response).to render_template :show
expect(response.content_type).to eq 'text/html'
end
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
end
it 'returns a not found 404 response' do
read_board board: board
expect(response).to have_gitlab_http_status(404)
expect(response.content_type).to eq 'text/html'
end
end
end
context 'when format is JSON' do
it 'returns project board' do
read_board board: board, format: :json
expect(response).to match_response_schema('board')
end
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
end
it 'returns a not found 404 response' do
read_board board: board, format: :json
expect(response).to have_gitlab_http_status(404)
expect(response.content_type).to eq 'application/json'
end
end
end
context 'when board does not belong to group' do
it 'returns a not found 404 response' do
another_board = create(:board)
read_board board: another_board
expect(response).to have_gitlab_http_status(404)
end
end
def read_board(board:, format: :html)
get :show, group_id: group,
id: board.to_param,
format: format
end
end
end
......@@ -2,11 +2,13 @@ require 'spec_helper'
describe BoardsHelper do
describe '#build_issue_link_base' do
it 'returns correct path for project board' do
@project = create(:project)
@board = create(:board, project: @project)
context 'project board' do
it 'returns correct path for project board' do
@project = create(:project)
@board = create(:board, project: @project)
expect(build_issue_link_base).to eq("/#{@project.namespace.path}/#{@project.path}/issues")
expect(build_issue_link_base).to eq("/#{@project.namespace.path}/#{@project.path}/issues")
end
end
context 'group board' do
......
require 'spec_helper'
describe API::GroupBoards do
set(:user) { create(:user) }
set(:non_member) { create(:user) }
set(:guest) { create(:user) }
set(:admin) { create(:user, :admin) }
set(:board_parent) { create(:group, :public) }
before do
board_parent.add_owner(user)
end
set(:project) { create(:project, :public, namespace: board_parent ) }
set(:dev_label) do
create(:group_label, title: 'Development', color: '#FFAABB', group: board_parent)
end
set(:test_label) do
create(:group_label, title: 'Testing', color: '#FFAACC', group: board_parent)
end
set(:ux_label) do
create(:group_label, title: 'UX', color: '#FF0000', group: board_parent)
end
set(:dev_list) do
create(:list, label: dev_label, position: 1)
end
set(:test_list) do
create(:list, label: test_label, position: 2)
end
set(:milestone) { create(:milestone, group: board_parent) }
set(:board_label) { create(:group_label, group: board_parent) }
set(:board) { create(:board, group: board_parent, lists: [dev_list, test_list]) }
it_behaves_like 'group and project boards', "/groups/:id/boards", false
describe 'POST /groups/:id/boards/lists' do
let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" }
it 'does not create lists for child project labels' do
project_label = create(:label, project: project)
post api(url, user), label_id: project_label.id
expect(response).to have_gitlab_http_status(400)
end
end
end
......@@ -2,34 +2,20 @@ require 'spec_helper'
describe Boards::CreateService do
describe '#execute' do
let(:project) { create(:project) }
context 'when board parent is a project' do
let(:parent) { create(:project) }
subject(:service) { described_class.new(project, double) }
subject(:service) { described_class.new(parent, double) }
context 'when project does not have a board' do
it 'creates a new board' do
expect { service.execute }.to change(Board, :count).by(1)
end
it 'creates the default lists' do
board = service.execute
expect(board.lists.size).to eq 2
expect(board.lists.first).to be_backlog
expect(board.lists.last).to be_closed
end
it_behaves_like 'boards create service'
end
context 'when project has a board' do
before do
create(:board, project: project)
end
context 'when board parent is a group' do
let(:parent) { create(:group) }
it 'does not create a new board' do
expect(service).to receive(:can_create_board?) { false }
subject(:service) { described_class.new(parent, double) }
expect { service.execute }.not_to change(project.boards, :count)
end
it_behaves_like 'boards create service'
end
end
end
......@@ -2,108 +2,53 @@ require 'spec_helper'
describe Boards::Issues::MoveService do
describe '#execute' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:board1) { create(:board, project: project) }
let(:bug) { create(:label, project: project, name: 'Bug') }
let(:development) { create(:label, project: project, name: 'Development') }
let(:testing) { create(:label, project: project, name: 'Testing') }
let!(:list1) { create(:list, board: board1, label: development, position: 0) }
let!(:list2) { create(:list, board: board1, label: testing, position: 1) }
let!(:closed) { create(:closed_list, board: board1) }
before do
project.add_developer(user)
end
context 'when moving an issue between lists' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
it 'delegates the label changes to Issues::UpdateService' do
expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once
described_class.new(project, user, params).execute(issue)
end
it 'removes the label from the list it came from and adds the label of the list it goes to' do
described_class.new(project, user, params).execute(issue)
expect(issue.reload.labels).to contain_exactly(bug, testing)
end
end
context 'when moving to closed' do
context 'when parent is a project' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:board1) { create(:board, project: project) }
let(:board2) { create(:board, project: project) }
let(:bug) { create(:label, project: project, name: 'Bug') }
let(:development) { create(:label, project: project, name: 'Development') }
let(:testing) { create(:label, project: project, name: 'Testing') }
let(:regression) { create(:label, project: project, name: 'Regression') }
let!(:list3) { create(:list, board: board2, label: regression, position: 1) }
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
let!(:list1) { create(:list, board: board1, label: development, position: 0) }
let!(:list2) { create(:list, board: board1, label: testing, position: 1) }
let!(:closed) { create(:closed_list, board: board1) }
it 'delegates the close proceedings to Issues::CloseService' do
expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
let(:parent) { project }
described_class.new(project, user, params).execute(issue)
before do
parent.add_developer(user)
end
it 'removes all list-labels from project boards and close the issue' do
described_class.new(project, user, params).execute(issue)
issue.reload
expect(issue.labels).to contain_exactly(bug)
expect(issue).to be_closed
end
it_behaves_like 'issues move service'
end
context 'when moving from closed' do
let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
it 'delegates the re-open proceedings to Issues::ReopenService' do
expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
described_class.new(project, user, params).execute(issue)
end
it 'adds the label of the list it goes to and reopen the issue' do
described_class.new(project, user, params).execute(issue)
issue.reload
expect(issue.labels).to contain_exactly(bug, testing)
expect(issue).to be_opened
end
end
context 'when parent is a group' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:board1) { create(:board, group: group) }
let(:board2) { create(:board, group: group) }
context 'when moving to same list' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
let(:bug) { create(:group_label, group: group, name: 'Bug') }
let(:development) { create(:group_label, group: group, name: 'Development') }
let(:testing) { create(:group_label, group: group, name: 'Testing') }
let(:regression) { create(:group_label, group: group, name: 'Regression') }
it 'returns false' do
expect(described_class.new(project, user, params).execute(issue)).to eq false
end
let!(:list1) { create(:list, board: board1, label: development, position: 0) }
let!(:list2) { create(:list, board: board1, label: testing, position: 1) }
let!(:closed) { create(:closed_list, board: board1) }
it 'keeps issues labels' do
described_class.new(project, user, params).execute(issue)
let(:parent) { group }
expect(issue.reload.labels).to contain_exactly(bug, development)
before do
parent.add_developer(user)
end
it 'sorts issues' do
[issue, issue1, issue2].each do |issue|
issue.move_to_end && issue.save!
end
params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
described_class.new(project, user, params).execute(issue)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
it_behaves_like 'issues move service'
end
end
end
......@@ -2,37 +2,20 @@ require 'spec_helper'
describe Boards::ListService do
describe '#execute' do
let(:project) { create(:project) }
context 'when board parent is a project' do
let(:parent) { create(:project) }
subject(:service) { described_class.new(project, double) }
subject(:service) { described_class.new(parent, double) }
context 'when project does not have a board' do
it 'creates a new project board' do
expect { service.execute }.to change(project.boards, :count).by(1)
end
it 'delegates the project board creation to Boards::CreateService' do
expect_any_instance_of(Boards::CreateService).to receive(:execute).once
service.execute
end
it_behaves_like 'boards list service'
end
context 'when project has a board' do
before do
create(:board, project: project)
end
it 'does not create a new board' do
expect { service.execute }.not_to change(project.boards, :count)
end
end
context 'when board parent is a group' do
let(:parent) { create(:group) }
it 'returns project boards' do
board1 = create(:board, project: project)
board2 = create(:board, project: project)
subject(:service) { described_class.new(parent, double) }
expect(service.execute).to match_array [board1, board2]
it_behaves_like 'boards list service'
end
end
end
......@@ -2,62 +2,77 @@ require 'spec_helper'
describe Boards::Lists::CreateService do
describe '#execute' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
let(:label) { create(:label, project: project, name: 'in-progress') }
shared_examples 'creating board lists' do
let(:user) { create(:user) }
subject(:service) { described_class.new(project, user, label_id: label.id) }
subject(:service) { described_class.new(parent, user, label_id: label.id) }
before do
project.add_developer(user)
end
before do
parent.add_developer(user)
end
context 'when board lists is empty' do
it 'creates a new list at beginning of the list' do
list = service.execute(board)
context 'when board lists is empty' do
it 'creates a new list at beginning of the list' do
list = service.execute(board)
expect(list.position).to eq 0
expect(list.position).to eq 0
end
end
end
context 'when board lists has the done list' do
it 'creates a new list at beginning of the list' do
list = service.execute(board)
context 'when board lists has the done list' do
it 'creates a new list at beginning of the list' do
list = service.execute(board)
expect(list.position).to eq 0
expect(list.position).to eq 0
end
end
end
context 'when board lists has labels lists' do
it 'creates a new list at end of the lists' do
create(:list, board: board, position: 0)
create(:list, board: board, position: 1)
context 'when board lists has labels lists' do
it 'creates a new list at end of the lists' do
create(:list, board: board, position: 0)
create(:list, board: board, position: 1)
list = service.execute(board)
list = service.execute(board)
expect(list.position).to eq 2
expect(list.position).to eq 2
end
end
end
context 'when board lists has label and done lists' do
it 'creates a new list at end of the label lists' do
list1 = create(:list, board: board, position: 0)
context 'when board lists has label and done lists' do
it 'creates a new list at end of the label lists' do
list1 = create(:list, board: board, position: 0)
list2 = service.execute(board)
list2 = service.execute(board)
expect(list1.reload.position).to eq 0
expect(list2.reload.position).to eq 1
expect(list1.reload.position).to eq 0
expect(list2.reload.position).to eq 1
end
end
end
context 'when provided label does not belongs to the project' do
it 'raises an error' do
label = create(:label, name: 'in-development')
service = described_class.new(project, user, label_id: label.id)
context 'when provided label does not belongs to the parent' do
it 'raises an error' do
label = create(:label, name: 'in-development')
service = described_class.new(parent, user, label_id: label.id)
expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound)
expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
context 'when board parent is a project' do
let(:parent) { create(:project) }
let(:board) { create(:board, project: parent) }
let(:label) { create(:label, project: parent, name: 'in-progress') }
it_behaves_like 'creating board lists'
end
context 'when board parent is a group' do
let(:parent) { create(:group) }
let(:board) { create(:board, group: parent) }
let(:label) { create(:group_label, group: parent, name: 'in-progress') }
it_behaves_like 'creating board lists'
end
end
end
......@@ -2,37 +2,24 @@ require 'spec_helper'
describe Boards::Lists::DestroyService do
describe '#execute' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
context 'when board parent is a project' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
context 'when list type is label' do
it 'removes list from board' do
list = create(:list, board: board)
service = described_class.new(project, user)
let(:parent) { project }
expect { service.execute(list) }.to change(board.lists, :count).by(-1)
end
it 'decrements position of higher lists' do
development = create(:list, board: board, position: 0)
review = create(:list, board: board, position: 1)
staging = create(:list, board: board, position: 2)
closed = board.closed_list
described_class.new(project, user).execute(development)
expect(review.reload.position).to eq 0
expect(staging.reload.position).to eq 1
expect(closed.reload.position).to be_nil
end
it_behaves_like 'lists destroy service'
end
it 'does not remove list from board when list type is closed' do
list = board.closed_list
service = described_class.new(project, user)
context 'when board parent is a group' do
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
let(:user) { create(:user) }
let(:parent) { group }
expect { service.execute(list) }.not_to change(board.lists, :count)
it_behaves_like 'lists destroy service'
end
end
end
require 'spec_helper'
describe Boards::Lists::ListService do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:label) { create(:label, project: project) }
let!(:list) { create(:list, board: board, label: label) }
let(:service) { described_class.new(project, double) }
describe '#execute' do
context 'when the board has a backlog list' do
let!(:backlog_list) { create(:backlog_list, board: board) }
it 'does not create a backlog list' do
expect { service.execute(board) }.not_to change(board.lists, :count)
end
context 'when board parent is a project' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:label) { create(:label, project: project) }
let!(:list) { create(:list, board: board, label: label) }
let(:service) { described_class.new(project, double) }
it "returns board's lists" do
expect(service.execute(board)).to eq [backlog_list, list, board.closed_list]
end
it_behaves_like 'lists list service'
end
context 'when the board does not have a backlog list' do
it 'creates a backlog list' do
expect { service.execute(board) }.to change(board.lists, :count).by(1)
end
context 'when board parent is a group' do
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
let(:label) { create(:group_label, group: group) }
let!(:list) { create(:list, board: board, label: label) }
let(:service) { described_class.new(group, double) }
it "returns board's lists" do
expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list]
end
it_behaves_like 'lists list service'
end
end
end
......@@ -2,100 +2,24 @@ require 'spec_helper'
describe Boards::Lists::MoveService do
describe '#execute' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
context 'when board parent is a project' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
let!(:planning) { create(:list, board: board, position: 0) }
let!(:development) { create(:list, board: board, position: 1) }
let!(:review) { create(:list, board: board, position: 2) }
let!(:staging) { create(:list, board: board, position: 3) }
let!(:closed) { create(:closed_list, board: board) }
let(:parent) { project }
context 'when list type is set to label' do
it 'keeps position of lists when new position is nil' do
service = described_class.new(project, user, position: nil)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is equal to old position' do
service = described_class.new(project, user, position: planning.position)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is negative' do
service = described_class.new(project, user, position: -1)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is equal to number of labels lists' do
service = described_class.new(project, user, position: board.lists.label.size)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is greater than number of labels lists' do
service = described_class.new(project, user, position: board.lists.label.size + 1)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'increments position of intermediate lists when new positon is equal to first position' do
service = described_class.new(project, user, position: 0)
service.execute(staging)
expect(current_list_positions).to eq [1, 2, 3, 0]
end
it 'decrements position of intermediate lists when new positon is equal to last position' do
service = described_class.new(project, user, position: board.lists.label.last.position)
service.execute(planning)
expect(current_list_positions).to eq [3, 0, 1, 2]
end
it 'decrements position of intermediate lists when new position is greater than old position' do
service = described_class.new(project, user, position: 2)
service.execute(planning)
expect(current_list_positions).to eq [2, 0, 1, 3]
end
it 'increments position of intermediate lists when new position is lower than old position' do
service = described_class.new(project, user, position: 1)
service.execute(staging)
expect(current_list_positions).to eq [0, 2, 3, 1]
end
it_behaves_like 'lists move service'
end
it 'keeps position of lists when list type is closed' do
service = described_class.new(project, user, position: 2)
context 'when board parent is a group' do
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
let(:user) { create(:user) }
service.execute(closed)
let(:parent) { group }
expect(current_list_positions).to eq [0, 1, 2, 3]
it_behaves_like 'lists move service'
end
end
def current_list_positions
[planning, development, review, staging].map { |list| list.reload.position }
end
end
shared_examples 'boards create service' do
context 'when parent does not have a board' do
it 'creates a new board' do
expect { service.execute }.to change(Board, :count).by(1)
end
it 'creates the default lists' do
board = service.execute
expect(board.lists.size).to eq 2
expect(board.lists.first).to be_backlog
expect(board.lists.last).to be_closed
end
end
context 'when parent has a board' do
before do
create(:board, parent: parent)
end
it 'does not create a new board' do
expect(service).to receive(:can_create_board?) { false }
expect { service.execute }.not_to change(parent.boards, :count)
end
end
end
shared_examples 'boards list service' do
context 'when parent does not have a board' do
it 'creates a new parent board' do
expect { service.execute }.to change(parent.boards, :count).by(1)
end
it 'delegates the parent board creation to Boards::CreateService' do
expect_any_instance_of(Boards::CreateService).to receive(:execute).once
service.execute
end
end
context 'when parent has a board' do
before do
create(:board, parent: parent)
end
it 'does not create a new board' do
expect { service.execute }.not_to change(parent.boards, :count)
end
end
it 'returns parent boards' do
board = create(:board, parent: parent)
expect(service.execute).to eq [board]
end
end
shared_examples 'issues list service' do
it 'delegates search to IssuesFinder' do
params = { board_id: board.id, id: list1.id }
expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original
described_class.new(parent, user, params).execute
end
context 'issues are ordered by priority' do
it 'returns opened issues when list_id is missing' do
params = { board_id: board.id }
issues = described_class.new(parent, user, params).execute
expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
end
it 'returns opened issues when listing issues from Backlog' do
params = { board_id: board.id, id: backlog.id }
issues = described_class.new(parent, user, params).execute
expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
end
it 'returns closed issues when listing issues from Closed' do
params = { board_id: board.id, id: closed.id }
issues = described_class.new(parent, user, params).execute
expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1]
end
it 'returns opened issues that have label list applied when listing issues from a label list' do
params = { board_id: board.id, id: list1.id }
issues = described_class.new(parent, user, params).execute
expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2]
end
end
context 'with list that does not belong to the board' do
it 'raises an error' do
list = create(:list)
service = described_class.new(parent, user, board_id: board.id, id: list.id)
expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with invalid list id' do
it 'raises an error' do
service = described_class.new(parent, user, board_id: board.id, id: nil)
expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
shared_examples 'issues move service' do
context 'when moving an issue between lists' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
it 'delegates the label changes to Issues::UpdateService' do
expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once
described_class.new(parent, user, params).execute(issue)
end
it 'removes the label from the list it came from and adds the label of the list it goes to' do
described_class.new(parent, user, params).execute(issue)
expect(issue.reload.labels).to contain_exactly(bug, testing)
end
end
context 'when moving to closed' do
let!(:list3) { create(:list, board: board2, label: regression, position: 1) }
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
it 'delegates the close proceedings to Issues::CloseService' do
expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
described_class.new(parent, user, params).execute(issue)
end
it 'removes all list-labels from boards and close the issue' do
described_class.new(parent, user, params).execute(issue)
issue.reload
expect(issue.labels).to contain_exactly(bug)
expect(issue).to be_closed
end
end
context 'when moving from closed' do
let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
it 'delegates the re-open proceedings to Issues::ReopenService' do
expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
described_class.new(parent, user, params).execute(issue)
end
it 'adds the label of the list it goes to and reopen the issue' do
described_class.new(parent, user, params).execute(issue)
issue.reload
expect(issue.labels).to contain_exactly(bug, testing)
expect(issue).to be_opened
end
end
context 'when moving to same list' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
it 'returns false' do
expect(described_class.new(parent, user, params).execute(issue)).to eq false
end
it 'keeps issues labels' do
described_class.new(parent, user, params).execute(issue)
expect(issue.reload.labels).to contain_exactly(bug, development)
end
it 'sorts issues' do
[issue, issue1, issue2].each do |issue|
issue.move_to_end && issue.save!
end
params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
described_class.new(parent, user, params).execute(issue)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
end
end
shared_examples 'lists destroy service' do
context 'when list type is label' do
it 'removes list from board' do
list = create(:list, board: board)
service = described_class.new(parent, user)
expect { service.execute(list) }.to change(board.lists, :count).by(-1)
end
it 'decrements position of higher lists' do
development = create(:list, board: board, position: 0)
review = create(:list, board: board, position: 1)
staging = create(:list, board: board, position: 2)
closed = board.closed_list
described_class.new(parent, user).execute(development)
expect(review.reload.position).to eq 0
expect(staging.reload.position).to eq 1
expect(closed.reload.position).to be_nil
end
end
it 'does not remove list from board when list type is closed' do
list = board.closed_list
service = described_class.new(parent, user)
expect { service.execute(list) }.not_to change(board.lists, :count)
end
end
shared_examples 'lists list service' do
context 'when the board has a backlog list' do
let!(:backlog_list) { create(:backlog_list, board: board) }
it 'does not create a backlog list' do
expect { service.execute(board) }.not_to change(board.lists, :count)
end
it "returns board's lists" do
expect(service.execute(board)).to eq [backlog_list, list, board.closed_list]
end
end
context 'when the board does not have a backlog list' do
it 'creates a backlog list' do
expect { service.execute(board) }.to change(board.lists, :count).by(1)
end
it "returns board's lists" do
expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list]
end
end
end
shared_examples 'lists move service' do
let!(:planning) { create(:list, board: board, position: 0) }
let!(:development) { create(:list, board: board, position: 1) }
let!(:review) { create(:list, board: board, position: 2) }
let!(:staging) { create(:list, board: board, position: 3) }
let!(:closed) { create(:closed_list, board: board) }
context 'when list type is set to label' do
it 'keeps position of lists when new position is nil' do
service = described_class.new(parent, user, position: nil)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is equal to old position' do
service = described_class.new(parent, user, position: planning.position)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is negative' do
service = described_class.new(parent, user, position: -1)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is equal to number of labels lists' do
service = described_class.new(parent, user, position: board.lists.label.size)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'keeps position of lists when new positon is greater than number of labels lists' do
service = described_class.new(parent, user, position: board.lists.label.size + 1)
service.execute(planning)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
it 'increments position of intermediate lists when new positon is equal to first position' do
service = described_class.new(parent, user, position: 0)
service.execute(staging)
expect(current_list_positions).to eq [1, 2, 3, 0]
end
it 'decrements position of intermediate lists when new positon is equal to last position' do
service = described_class.new(parent, user, position: board.lists.label.last.position)
service.execute(planning)
expect(current_list_positions).to eq [3, 0, 1, 2]
end
it 'decrements position of intermediate lists when new position is greater than old position' do
service = described_class.new(parent, user, position: 2)
service.execute(planning)
expect(current_list_positions).to eq [2, 0, 1, 3]
end
it 'increments position of intermediate lists when new position is lower than old position' do
service = described_class.new(parent, user, position: 1)
service.execute(staging)
expect(current_list_positions).to eq [0, 2, 3, 1]
end
end
it 'keeps position of lists when list type is closed' do
service = described_class.new(parent, user, position: 2)
service.execute(closed)
expect(current_list_positions).to eq [0, 1, 2, 3]
end
def current_list_positions
[planning, development, review, staging].map { |list| list.reload.position }
end
end
......@@ -20,7 +20,7 @@ describe 'layouts/nav/sidebar/_project' do
it 'has board tab when multiple issue boards is not available' do
allow(License).to receive(:feature_available?).and_call_original
allow(License).to receive(:feature_available?).with(:multiple_issue_boards) { false }
allow(License).to receive(:feature_available?).with(:multiple_project_issue_boards) { false }
render
......
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