Commit 5b90f10c authored by Stan Hu's avatar Stan Hu

Merge branch 'epic_board_list_create' into 'master'

Create epic board lists

See merge request gitlab-org/gitlab!53125
parents 434970a8 8d2e1d05
# 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": [
],
......@@ -25287,6 +25287,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",
......@@ -46077,6 +46199,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