Commit ad96ac9d authored by Felipe Artur's avatar Felipe Artur

EE PORT Bring one group issue board to CE

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