Commit 8d2e1d05 authored by Jan Provaznik's avatar Jan Provaznik Committed by Stan Hu

Allow creating epic board lists

Adds mutation and service for creating epic board lists.  Because most
of the functionality is same as for issue boar lists, it also moves
shared logic into a parent class.
parent 41f7a751
# frozen_string_literal: true
module Mutations
module Boards
module Lists
class BaseCreate < BaseMutation
argument :backlog, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Create the backlog list.'
argument :label_id, ::Types::GlobalIDType[::Label],
required: false,
description: 'Global ID of an existing label.'
def ready?(**args)
if args.slice(*mutually_exclusive_args).size != 1
arg_str = mutually_exclusive_args.map { |x| x.to_s.camelize(:lower) }.join(' or ')
raise Gitlab::Graphql::Errors::ArgumentError, "one and only one of #{arg_str} is required"
end
super
end
def resolve(**args)
board = authorized_find!(id: args[:board_id])
params = create_list_params(args)
response = create_list(board, params)
{
list: response.success? ? response.payload[:list] : nil,
errors: response.errors
}
end
private
def create_list(board, params)
raise NotImplementedError
end
def create_list_params(args)
params = args.slice(*mutually_exclusive_args).with_indifferent_access
params[:label_id] &&= ::GitlabSchema.parse_gid(params[:label_id], expected_type: ::Label).model_id
params
end
def mutually_exclusive_args
[:backlog, :label_id]
end
end
end
end
end
......@@ -3,59 +3,32 @@
module Mutations
module Boards
module Lists
class Create < Base
class Create < BaseCreate
graphql_name 'BoardListCreate'
argument :backlog, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Create the backlog list.'
argument :board_id, ::Types::GlobalIDType[::Board],
required: true,
description: 'Global ID of the issue board to mutate.'
argument :label_id, ::Types::GlobalIDType[::Label],
required: false,
description: 'Global ID of an existing label.'
field :list,
Types::BoardListType,
null: true,
description: 'Issue list in the issue board.'
def ready?(**args)
if args.slice(*mutually_exclusive_args).size != 1
arg_str = mutually_exclusive_args.map { |x| x.to_s.camelize(:lower) }.join(' or ')
raise Gitlab::Graphql::Errors::ArgumentError, "one and only one of #{arg_str} is required"
end
authorize :admin_list
super
end
def resolve(**args)
board = authorized_find!(id: args[:board_id])
params = create_list_params(args)
response = create_list(board, params)
private
{
list: response.success? ? response.payload[:list] : nil,
errors: response.errors
}
def find_object(id:)
GitlabSchema.object_from_id(id, expected_type: ::Board)
end
private
def create_list(board, params)
create_list_service =
::Boards::Lists::CreateService.new(board.resource_parent, current_user, params)
create_list_service.execute(board)
end
# Overridden in EE
def create_list_params(args)
params = args.slice(*mutually_exclusive_args).with_indifferent_access
params[:label_id] &&= ::GitlabSchema.parse_gid(params[:label_id], expected_type: ::Label).model_id
params
end
# Overridden in EE
def mutually_exclusive_args
[:backlog, :label_id]
end
end
end
end
......
# frozen_string_literal: true
module Boards
module Lists
# This class is used by issue and epic board lists
# for creating new list
class BaseCreateService < Boards::BaseService
include Gitlab::Utils::StrongMemoize
def execute(board)
list = case type
when :backlog
create_backlog(board)
else
target = target(board)
position = next_position(board)
return ServiceResponse.error(message: _('%{board_target} not found') % { board_target: type.to_s.capitalize }) if target.blank?
create_list(board, type, target, position)
end
return ServiceResponse.error(message: list.errors.full_messages) unless list.persisted?
ServiceResponse.success(payload: { list: list })
end
private
def type
# We don't ever expect to have more than one list
# type param at once.
if params.key?('backlog')
:backlog
else
:label
end
end
def target(board)
strong_memoize(:target) do
available_labels.find_by(id: params[:label_id]) # rubocop: disable CodeReuse/ActiveRecord
end
end
def available_labels
::Labels::AvailableLabelsService.new(current_user, parent, {})
.available_labels
end
def next_position(board)
max_position = board.lists.movable.maximum(:position)
max_position.nil? ? 0 : max_position.succ
end
def create_list(board, type, target, position)
board.lists.create(create_list_attributes(type, target, position))
end
def create_list_attributes(type, target, position)
{ type => target, list_type: type, position: position }
end
def create_backlog(board)
return board.lists.backlog.first if board.lists.backlog.exists?
board.lists.create(list_type: :backlog, position: nil)
end
end
end
end
......@@ -2,68 +2,7 @@
module Boards
module Lists
class CreateService < Boards::BaseService
include Gitlab::Utils::StrongMemoize
def execute(board)
list = case type
when :backlog
create_backlog(board)
else
target = target(board)
position = next_position(board)
return ServiceResponse.error(message: _('%{board_target} not found') % { board_target: type.to_s.capitalize }) if target.blank?
create_list(board, type, target, position)
end
return ServiceResponse.error(message: list.errors.full_messages) unless list.persisted?
ServiceResponse.success(payload: { list: list })
end
private
def type
# We don't ever expect to have more than one list
# type param at once.
if params.key?('backlog')
:backlog
else
:label
end
end
def target(board)
strong_memoize(:target) do
available_labels.find_by(id: params[:label_id]) # rubocop: disable CodeReuse/ActiveRecord
end
end
def available_labels
::Labels::AvailableLabelsService.new(current_user, parent, {})
.available_labels
end
def next_position(board)
max_position = board.lists.movable.maximum(:position)
max_position.nil? ? 0 : max_position.succ
end
def create_list(board, type, target, position)
board.lists.create(create_list_attributes(type, target, position))
end
def create_list_attributes(type, target, position)
{ type => target, list_type: type, position: position }
end
def create_backlog(board)
return board.lists.backlog.first if board.lists.backlog.exists?
board.lists.create(list_type: :backlog, position: nil)
end
class CreateService < Boards::Lists::BaseCreateService
end
end
end
......
......@@ -2361,7 +2361,7 @@ type BoardListCreatePayload {
errors: [String!]!
"""
List of the issue board.
Issue list in the issue board.
"""
list: BoardList
}
......@@ -9155,6 +9155,51 @@ type EpicBoardEdge {
node: EpicBoard
}
"""
Autogenerated input type of EpicBoardListCreate
"""
input EpicBoardListCreateInput {
"""
Create the backlog list.
"""
backlog: Boolean
"""
Global ID of the issue board to mutate.
"""
boardId: BoardsEpicBoardID!
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Global ID of an existing label.
"""
labelId: LabelID
}
"""
Autogenerated return type of EpicBoardListCreate
"""
type EpicBoardListCreatePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
Epic list in the epic board.
"""
list: EpicList
}
"""
The connection type for Epic.
"""
......@@ -16235,6 +16280,7 @@ type Mutation {
environmentsCanaryIngressUpdate(input: EnvironmentsCanaryIngressUpdateInput!): EnvironmentsCanaryIngressUpdatePayload
epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload
epicBoardCreate(input: EpicBoardCreateInput!): EpicBoardCreatePayload
epicBoardListCreate(input: EpicBoardListCreateInput!): EpicBoardListCreatePayload
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
exportRequirements(input: ExportRequirementsInput!): ExportRequirementsPayload
......
......@@ -6045,20 +6045,6 @@
"description": "Autogenerated input type of BoardListCreate",
"fields": null,
"inputFields": [
{
"name": "boardId",
"description": "Global ID of the issue board to mutate.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "BoardID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "backlog",
"description": "Create the backlog list.",
......@@ -6079,6 +6065,20 @@
},
"defaultValue": null
},
{
"name": "boardId",
"description": "Global ID of the issue board to mutate.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "BoardID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "milestoneId",
"description": "Global ID of an existing milestone.",
......@@ -6171,7 +6171,7 @@
},
{
"name": "list",
"description": "List of the issue board.",
"description": "Issue list in the issue board.",
"args": [
],
......@@ -25291,6 +25291,128 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "EpicBoardListCreateInput",
"description": "Autogenerated input type of EpicBoardListCreate",
"fields": null,
"inputFields": [
{
"name": "backlog",
"description": "Create the backlog list.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": null
},
{
"name": "labelId",
"description": "Global ID of an existing label.",
"type": {
"kind": "SCALAR",
"name": "LabelID",
"ofType": null
},
"defaultValue": null
},
{
"name": "boardId",
"description": "Global ID of the issue board to mutate.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "BoardsEpicBoardID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EpicBoardListCreatePayload",
"description": "Autogenerated return type of EpicBoardListCreate",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "list",
"description": "Epic list in the epic board.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "EpicList",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "EpicConnection",
......@@ -46081,6 +46203,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "epicBoardListCreate",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "EpicBoardListCreateInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "EpicBoardListCreatePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "epicSetSubscription",
"description": null,
......@@ -357,7 +357,7 @@ Autogenerated return type of BoardListCreate.
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `list` | BoardList | List of the issue board. |
| `list` | BoardList | Issue list in the issue board. |
### BoardListUpdateLimitMetricsPayload
......@@ -1482,6 +1482,16 @@ Autogenerated return type of EpicBoardCreate.
| `epicBoard` | EpicBoard | The created epic board. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
### EpicBoardListCreatePayload
Autogenerated return type of EpicBoardListCreate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `list` | EpicList | Epic list in the epic board. |
### EpicDescendantCount
Counts of descendent epics.
......
......@@ -37,6 +37,7 @@ module EE
mount_mutation ::Mutations::Boards::Update
mount_mutation ::Mutations::Boards::UpdateEpicUserPreferences
mount_mutation ::Mutations::Boards::EpicBoards::Create
mount_mutation ::Mutations::Boards::EpicLists::Create
mount_mutation ::Mutations::Boards::Lists::UpdateLimitMetrics
mount_mutation ::Mutations::InstanceSecurityDashboard::AddProject
mount_mutation ::Mutations::InstanceSecurityDashboard::RemoveProject
......
......@@ -2,25 +2,32 @@
module Mutations
module Boards
module Lists
class Base < BaseMutation
include Mutations::ResolvesIssuable
module EpicLists
class Create < ::Mutations::Boards::Lists::BaseCreate
graphql_name 'EpicBoardListCreate'
argument :board_id, ::Types::GlobalIDType[::Board],
argument :board_id, ::Types::GlobalIDType[::Boards::EpicBoard],
required: true,
description: 'Global ID of the issue board to mutate.'
field :list,
Types::BoardListType,
Types::Boards::EpicListType,
null: true,
description: 'List of the issue board.'
description: 'Epic list in the epic board.'
authorize :admin_list
authorize :admin_epic_list
private
def find_object(id:)
GitlabSchema.object_from_id(id, expected_type: ::Board)
GitlabSchema.object_from_id(id, expected_type: ::Boards::EpicBoard)
end
def create_list(board, params)
create_list_service =
::Boards::EpicLists::CreateService.new(board.group, current_user, params)
create_list_service.execute(board)
end
end
end
......
......@@ -198,6 +198,7 @@ module EE
enable :read_confidential_epic
enable :destroy_epic_link
enable :admin_epic_board
enable :admin_epic_list
end
rule { reporter & subepics_available }.policy do
......@@ -214,6 +215,7 @@ module EE
prevent :admin_epic
prevent :update_epic
prevent :destroy_epic
prevent :admin_epic_list
end
rule { auditor }.policy do
......
# frozen_string_literal: true
module Boards
module EpicLists
class CreateService < ::Boards::Lists::BaseCreateService
extend ::Gitlab::Utils::Override
override :execute
def execute(board)
unless Feature.enabled?(:epic_boards, board.group)
return ServiceResponse.error(message: 'Epic boards feature is not enabled.')
end
super
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Boards::EpicLists::Create do
let_it_be(:group) { create(:group, :private) }
let_it_be(:board) { create(:epic_board, group: group) }
before do
stub_licensed_features(epics: true)
end
it_behaves_like 'board lists create mutation'
end
......@@ -7,7 +7,8 @@ RSpec.describe GroupPolicy do
let(:epic_rules) do
%i(read_epic create_epic admin_epic destroy_epic read_confidential_epic
destroy_epic_link read_epic_board read_epic_list admin_epic_board)
destroy_epic_link read_epic_board read_epic_list admin_epic_board
admin_epic_list)
end
context 'when epics feature is disabled' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Create a label or backlog board list' do
let_it_be(:group) { create(:group, :private) }
let_it_be(:board) { create(:epic_board, group: group) }
before do
stub_licensed_features(epics: true)
end
it_behaves_like 'board lists create request' do
let(:mutation_name) { :epic_board_list_create }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Boards::EpicLists::CreateService do
let_it_be(:parent) { create(:group) }
let_it_be(:board) { create(:epic_board, group: parent) }
let_it_be(:label) { create(:group_label, group: parent, name: 'in-progress') }
it_behaves_like 'board lists create service' do
def create_list(params)
create(:epic_list, params.merge(epic_board: board))
end
end
context 'when epic_boards feature flag is disabled' do
before do
stub_feature_flags(epic_boards: false)
end
it 'returns an error' do
response = described_class.new(parent, nil).execute(board)
expect(response.success?).to eq(false)
expect(response.errors).to include("Epic boards feature is not enabled.")
end
end
end
......@@ -3,84 +3,8 @@
require 'spec_helper'
RSpec.describe Mutations::Boards::Lists::Create do
include GraphqlHelpers
let_it_be(:group) { create(:group, :private) }
let_it_be(:board) { create(:board, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:guest) { create(:user) }
let(:current_user) { user }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
let(:list_create_params) { {} }
before_all do
group.add_reporter(user)
group.add_guest(guest)
end
subject { mutation.resolve(board_id: board.to_global_id.to_s, **list_create_params) }
describe '#ready?' do
it 'raises an error if required arguments are missing' do
expect { mutation.ready?(board_id: 'some id') }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
it 'raises an error if too many required arguments are specified' do
expect { mutation.ready?(board_id: 'some id', backlog: true, label_id: 'some label') }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end
describe '#resolve' do
context 'with proper permissions' do
describe 'backlog list' do
let(:list_create_params) { { backlog: true } }
it 'creates one and only one backlog' do
expect { subject }.to change { board.lists.backlog.count }.from(0).to(1)
expect(board.lists.backlog.first.list_type).to eq 'backlog'
backlog_id = board.lists.backlog.first.id
expect { subject }.not_to change { board.lists.backlog.count }
expect(board.lists.backlog.last.id).to eq backlog_id
end
end
describe 'label list' do
let_it_be(:dev_label) do
create(:group_label, title: 'Development', color: '#FFAABB', group: group)
end
let(:list_create_params) { { label_id: dev_label.to_global_id.to_s } }
it 'creates a new issue board list for labels' do
expect { subject }.to change { board.lists.count }.from(1).to(2)
new_list = subject[:list]
expect(new_list.title).to eq dev_label.title
expect(new_list.position).to eq 0
end
context 'when label not found' do
let(:list_create_params) { { label_id: "gid://gitlab/Label/#{non_existing_record_id}" } }
it 'returns an error' do
expect(subject[:errors]).to include 'Label not found'
end
end
end
end
context 'without proper permissions' do
let(:current_user) { guest }
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
it_behaves_like 'board lists create mutation'
end
......@@ -3,52 +3,10 @@
require 'spec_helper'
RSpec.describe 'Create a label or backlog board list' do
include GraphqlHelpers
let_it_be(:group) { create(:group, :private) }
let_it_be(:board) { create(:board, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:dev_label) do
create(:group_label, title: 'Development', color: '#FFAABB', group: group)
end
let(:current_user) { user }
let(:mutation) { graphql_mutation(:board_list_create, input) }
let(:mutation_response) { graphql_mutation_response(:board_list_create) }
context 'the user is not allowed to read board lists' do
let(:input) { { board_id: board.to_global_id.to_s, backlog: true } }
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to admin board lists' do
before do
group.add_reporter(current_user)
end
describe 'backlog list' do
let(:input) { { board_id: board.to_global_id.to_s, backlog: true } }
it 'creates the list' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['list'])
.to include('position' => nil, 'listType' => 'backlog')
end
end
describe 'label list' do
let(:input) { { board_id: board.to_global_id.to_s, label_id: dev_label.to_global_id.to_s } }
it 'creates the list' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['list'])
.to include('position' => 0, 'listType' => 'label', 'label' => include('title' => 'Development'))
end
end
it_behaves_like 'board lists create request' do
let(:mutation_name) { :board_list_create }
end
end
......@@ -3,99 +3,23 @@
require 'spec_helper'
RSpec.describe Boards::Lists::CreateService do
describe '#execute' do
shared_examples 'creating board lists' do
let_it_be(:user) { create(:user) }
context 'when board parent is a project' do
let_it_be(:parent) { create(:project) }
let_it_be(:board) { create(:board, project: parent) }
let_it_be(:label) { create(:label, project: parent, name: 'in-progress') }
before_all do
parent.add_developer(user)
end
subject(:service) { described_class.new(parent, user, label_id: label.id) }
context 'when board lists is empty' do
it 'creates a new list at beginning of the list' do
response = service.execute(board)
expect(response.success?).to eq(true)
expect(response.payload[: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
response = service.execute(board)
expect(response.success?).to eq(true)
expect(response.payload[: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)
response = service.execute(board)
expect(response.success?).to eq(true)
expect(response.payload[: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).payload[:list]
expect(list1.reload.position).to eq 0
expect(list2.reload.position).to eq 1
end
end
context 'when provided label does not belong to the parent' do
it 'returns an error' do
label = create(:label, name: 'in-development')
service = described_class.new(parent, user, label_id: label.id)
response = service.execute(board)
expect(response.success?).to eq(false)
expect(response.errors).to include('Label not found')
end
end
context 'when backlog param is sent' do
it 'creates one and only one backlog list' do
service = described_class.new(parent, user, 'backlog' => true)
list = service.execute(board).payload[:list]
expect(list.list_type).to eq('backlog')
expect(list.position).to be_nil
expect(list).to be_valid
another_backlog = service.execute(board).payload[:list]
expect(another_backlog).to eq list
end
end
end
context 'when board parent is a project' do
let_it_be(:parent) { create(:project) }
let_it_be(:board) { create(:board, project: parent) }
let_it_be(:label) { create(:label, project: parent, name: 'in-progress') }
it_behaves_like 'board lists create service'
end
it_behaves_like 'creating board lists'
end
context 'when board parent is a group' do
let_it_be(:parent) { create(:group) }
let_it_be(:board) { create(:board, group: parent) }
let_it_be(:label) { create(:group_label, group: parent, name: 'in-progress') }
context 'when board parent is a group' do
let_it_be(:parent) { create(:group) }
let_it_be(:board) { create(:board, group: parent) }
let_it_be(:label) { create(:group_label, group: parent, name: 'in-progress') }
it_behaves_like 'board lists create service'
end
it_behaves_like 'creating board lists'
end
def create_list(params)
create(:list, params.merge(board: board))
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples 'board lists create mutation' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:list_create_params) { {} }
subject { mutation.resolve(board_id: board.to_global_id.to_s, **list_create_params) }
describe '#ready?' do
it 'raises an error if required arguments are missing' do
expect { mutation.ready?(board_id: 'some id') }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
it 'raises an error if too many required arguments are specified' do
expect { mutation.ready?(board_id: 'some id', backlog: true, label_id: 'some label') }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end
describe '#resolve' do
context 'with proper permissions' do
before_all do
group.add_reporter(user)
end
describe 'backlog list' do
let(:list_create_params) { { backlog: true } }
it 'creates one and only one backlog' do
expect { subject }.to change { board.lists.backlog.count }.by(1)
expect(board.lists.backlog.first.list_type).to eq 'backlog'
backlog_id = board.lists.backlog.first.id
expect { subject }.not_to change { board.lists.backlog.count }
expect(board.lists.backlog.last.id).to eq backlog_id
end
end
describe 'label list' do
let_it_be(:dev_label) do
create(:group_label, title: 'Development', color: '#FFAABB', group: group)
end
let(:list_create_params) { { label_id: dev_label.to_global_id.to_s } }
it 'creates a new label board list' do
expect { subject }.to change { board.lists.count }.by(1)
new_list = subject[:list]
expect(new_list.title).to eq dev_label.title
expect(new_list.position).to eq 0
end
context 'when label not found' do
let(:list_create_params) { { label_id: "gid://gitlab/Label/#{non_existing_record_id}" } }
it 'returns an error' do
expect(subject[:errors]).to include 'Label not found'
end
end
end
end
context 'without proper permissions' do
before_all do
group.add_guest(user)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples 'board lists create request' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:dev_label) do
create(:group_label, title: 'Development', color: '#FFAABB', group: group)
end
let(:mutation) { graphql_mutation(mutation_name, input) }
let(:mutation_response) { graphql_mutation_response(mutation_name) }
context 'the user is not allowed to read board lists' do
let(:input) { { board_id: board.to_global_id.to_s, backlog: true } }
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to admin board lists' do
before do
group.add_reporter(current_user)
end
describe 'backlog list' do
let(:input) { { board_id: board.to_global_id.to_s, backlog: true } }
it 'creates the list' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['list'])
.to include('position' => nil, 'listType' => 'backlog')
end
end
describe 'label list' do
let(:input) { { board_id: board.to_global_id.to_s, label_id: dev_label.to_global_id.to_s } }
it 'creates the list' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['list'])
.to include('position' => 0, 'listType' => 'label', 'label' => include('title' => 'Development'))
end
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'board lists create service' do
describe '#execute' do
let_it_be(:user) { create(:user) }
before_all do
parent.add_developer(user)
end
subject(:service) { described_class.new(parent, user, label_id: label.id) }
context 'when board lists is empty' do
it 'creates a new list at beginning of the list' do
response = service.execute(board)
expect(response.success?).to eq(true)
expect(response.payload[: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
response = service.execute(board)
expect(response.success?).to eq(true)
expect(response.payload[: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(position: 0)
create_list(position: 1)
response = service.execute(board)
expect(response.success?).to eq(true)
expect(response.payload[: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(position: 0)
list2 = service.execute(board).payload[:list]
expect(list1.reload.position).to eq 0
expect(list2.reload.position).to eq 1
end
end
context 'when provided label does not belong to the parent' do
it 'returns an error' do
label = create(:label, name: 'in-development')
service = described_class.new(parent, user, label_id: label.id)
response = service.execute(board)
expect(response.success?).to eq(false)
expect(response.errors).to include('Label not found')
end
end
context 'when backlog param is sent' do
it 'creates one and only one backlog list' do
service = described_class.new(parent, user, 'backlog' => true)
list = service.execute(board).payload[:list]
expect(list.list_type).to eq('backlog')
expect(list.position).to be_nil
expect(list).to be_valid
another_backlog = service.execute(board).payload[:list]
expect(another_backlog).to eq list
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment