Commit c957a14e authored by Brett Walker's avatar Brett Walker

Create board list with milestone or assignee

using GraphQL
parent 7f5f7670
......@@ -8,7 +8,7 @@ module Mutations
argument :board_id, ::Types::GlobalIDType[::Board],
required: true,
description: 'The Global ID of the issue board to mutate'
description: 'Global ID of the issue board to mutate'
field :list,
Types::BoardListType,
......
......@@ -12,7 +12,7 @@ module Mutations
argument :label_id, ::Types::GlobalIDType[::Label],
required: false,
description: 'ID of an existing label'
description: 'Global ID of an existing label'
def ready?(**args)
if args.slice(*mutually_exclusive_args).size != 1
......@@ -39,6 +39,7 @@ module Mutations
private
# Overridden in EE
def authorize_list_type_resource!(board, params)
return unless params[:label_id]
......@@ -57,13 +58,15 @@ module Mutations
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]).model_id if params[:label_id]
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
......@@ -71,3 +74,5 @@ module Mutations
end
end
end
Mutations::Boards::Lists::Create.prepend_if_ee('::EE::Mutations::Boards::Lists::Create')
......@@ -1309,13 +1309,18 @@ type BoardListConnection {
Autogenerated input type of BoardListCreate
"""
input BoardListCreateInput {
"""
Global ID of an existing user
"""
assigneeId: UserID
"""
Create the backlog list
"""
backlog: Boolean
"""
The Global ID of the issue board to mutate
Global ID of the issue board to mutate
"""
boardId: BoardID!
......@@ -1325,9 +1330,14 @@ input BoardListCreateInput {
clientMutationId: String
"""
ID of an existing label
Global ID of an existing label
"""
labelId: LabelID
"""
Global ID of an existing milestone
"""
milestoneId: MilestoneID
}
"""
......@@ -17115,6 +17125,11 @@ type UserEdge {
node: User
}
"""
Identifier of User
"""
scalar UserID
type UserPermissions {
"""
Indicates the user can perform `create_snippet` on this resource
......
......@@ -3505,7 +3505,7 @@
"inputFields": [
{
"name": "boardId",
"description": "The Global ID of the issue board to mutate",
"description": "Global ID of the issue board to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
......@@ -3529,7 +3529,7 @@
},
{
"name": "labelId",
"description": "ID of an existing label",
"description": "Global ID of an existing label",
"type": {
"kind": "SCALAR",
"name": "LabelID",
......@@ -3537,6 +3537,26 @@
},
"defaultValue": null
},
{
"name": "milestoneId",
"description": "Global ID of an existing milestone",
"type": {
"kind": "SCALAR",
"name": "MilestoneID",
"ofType": null
},
"defaultValue": null
},
{
"name": "assigneeId",
"description": "Global ID of an existing user",
"type": {
"kind": "SCALAR",
"name": "UserID",
"ofType": null
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
......@@ -50327,6 +50347,16 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "UserID",
"description": "Identifier of User",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "UserPermissions",
# frozen_string_literal: true
module EE
module Mutations
module Boards
module Lists
module Create
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do
argument :milestone_id, ::Types::GlobalIDType[::Milestone],
required: false,
description: 'Global ID of an existing milestone'
argument :assignee_id, ::Types::GlobalIDType[::User],
required: false,
description: 'Global ID of an existing user'
end
private
# rubocop: disable CodeReuse/ActiveRecord
override :authorize_list_type_resource!
def authorize_list_type_resource!(board, params)
super
if params[:milestone_id]
milestones = ::Boards::MilestonesFinder.new(board, current_user).execute
unless milestones.where(id: params[:milestone_id]).exists?
raise ::Gitlab::Graphql::Errors::ArgumentError, 'Milestone not found!'
end
end
if params[:assignee_id]
users = ::Boards::UsersFinder.new(board, current_user).execute
unless users.where(user_id: params[:assignee_id]).exists?
raise ::Gitlab::Graphql::Errors::ArgumentError, 'User not found!'
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
override :create_list_params
def create_list_params(args)
params = super
params[:milestone_id] &&= ::GitlabSchema.parse_gid(params[:milestone_id], expected_type: ::Milestone).model_id
params[:assignee_id] &&= ::GitlabSchema.parse_gid(params[:assignee_id], expected_type: ::User).model_id
params
end
override :mutually_exclusive_args
def mutually_exclusive_args
super + [:milestone_id, :assignee_id]
end
end
end
end
end
end
---
title: Allow milestone and assignee board lists to be created using GraphQL
merge_request: 40551
author:
type: changed
# frozen_string_literal: true
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(:milestone) { create(:milestone, 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
before do
stub_licensed_features(board_assignee_lists: true, board_milestone_lists: true)
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 backlog or labelId or milestoneId or assigneeId is required')
end
it 'raises an error if too many required arguments are specified' do
expect { mutation.ready?({ board_id: 'some id', milestone_id: 'some milestone', assignee_id: 'some label' }) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError,
'one and only one of backlog or labelId or milestoneId or assigneeId is required')
end
end
describe '#resolve' do
context 'with proper permissions' do
describe 'milestone list' do
let(:list_create_params) { { milestone_id: milestone.to_global_id.to_s } }
context 'when feature unavailable' do
it 'returns an error' do
stub_licensed_features(board_milestone_lists: false)
expect(subject[:errors]).to include 'List type Milestone lists not available with your current license'
end
end
it 'creates a new issue board list for milestones' do
expect { subject }.to change { board.lists.count }.from(1).to(2)
new_list = subject[:list]
expect(new_list.title).to eq milestone.title
expect(new_list.milestone_id).to eq milestone.id
expect(new_list.position).to eq 0
end
context 'when milestone not found' do
let(:list_create_params) { { milestone_id: "gid://gitlab/Milestone/#{non_existing_record_id}" } }
it 'raises an error' do
expect { subject }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'Milestone not found!')
end
end
end
describe 'assignee list' do
let(:list_create_params) { { assignee_id: guest.to_global_id.to_s } }
context 'when feature unavailable' do
it 'returns an error' do
stub_licensed_features(board_assignee_lists: false)
expect(subject[:errors]).to include 'List type Assignee lists not available with your current license'
end
end
it 'creates a new issue board list for assignees' do
expect { subject }.to change { board.lists.count }.from(1).to(2)
new_list = subject[:list]
expect(new_list.title).to eq "@#{guest.username}"
expect(new_list.user_id).to eq guest.id
expect(new_list.position).to eq 0
end
context 'when user not found' do
let(:list_create_params) { { assignee_id: "gid://gitlab/User/#{non_existing_record_id}" } }
it 'raises an error' do
expect { subject }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'User 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
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Create a milestone or assignee 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(:guest) { create(:user) }
let_it_be(:milestone) { create(:milestone, group: group) }
let(:current_user) { user }
let(:mutation) { graphql_mutation(:board_list_create, input) }
let(:mutation_response) { graphql_mutation_response(:board_list_create) }
before do
stub_licensed_features(board_assignee_lists: true, board_milestone_lists: true)
end
context 'the user is not allowed to read board lists' do
let(:input) { { board_id: board.to_global_id.to_s, milestone_id: milestone.to_global_id.to_s } }
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)
group.add_guest(guest)
end
describe 'milestone list' do
let(:input) { { board_id: board.to_global_id.to_s, milestone_id: milestone.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' => 'milestone',
'milestone' => include('title' => milestone.title))
end
end
describe 'assignee list' do
let!(:input) { { board_id: board.to_global_id.to_s, assignee_id: guest.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' => 'assignee',
'assignee' => include('id' => guest.to_global_id.to_s))
end
end
end
end
......@@ -24,14 +24,12 @@ RSpec.describe Mutations::Boards::Lists::Create do
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 backlog or labelId is required')
.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 backlog or labelId is required')
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end
......@@ -66,6 +64,15 @@ RSpec.describe Mutations::Boards::Lists::Create do
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 'raises an error' do
expect { subject }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'Label not found!')
end
end
end
end
......
# frozen_string_literal: true
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
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