Commit b955e91c authored by Jarka Košanová's avatar Jarka Košanová

Create an issue board via GraphQL mutation

- add new mutation and specs
- add documentation
parent 154e2d66
# frozen_string_literal: true
module Mutations
module Boards
class Create < ::Mutations::BaseMutation
include Mutations::ResolvesGroup
include ResolvesProject
graphql_name 'CreateBoard'
field :board,
Types::BoardType,
null: true,
description: 'The board after mutation.'
argument :project_path, GraphQL::ID_TYPE,
required: false,
description: 'The project full path the board is associated with.'
argument :group_path, GraphQL::ID_TYPE,
required: false,
description: 'The group full path the board is associated with.'
argument :name,
GraphQL::STRING_TYPE,
required: false,
description: 'The board name.'
argument :assignee_id,
GraphQL::STRING_TYPE,
required: false,
description: 'The ID of the user to be assigned to the board.'
argument :milestone_id,
GraphQL::ID_TYPE,
required: false,
description: 'The ID of the milestone to be assigned to the board.'
argument :weight,
GraphQL::BOOLEAN_TYPE,
required: false,
description: 'The weight of the board.'
argument :label_ids,
[GraphQL::ID_TYPE],
required: false,
description: 'The IDs of labels to be added to the board.'
authorize :admin_board
def resolve(args)
group_path = args.delete(:group_path)
project_path = args.delete(:project_path)
board_parent = authorized_find!(group_path: group_path, project_path: project_path)
response = ::Boards::CreateService.new(board_parent, current_user, args).execute
{
board: response.payload,
errors: response.errors
}
end
def ready?(**args)
if args.values_at(:project_path, :group_path).compact.blank?
raise Gitlab::Graphql::Errors::ArgumentError,
'group_path or project_path arguments are required'
end
super
end
private
def find_object(group_path: nil, project_path: nil)
if group_path
resolve_group(full_path: group_path)
else
resolve_project(full_path: project_path)
end
end
end
end
end
......@@ -14,6 +14,7 @@ module Types
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::Boards::Create
mount_mutation Mutations::Boards::Destroy
mount_mutation Mutations::Boards::Issues::IssueMoveList
mount_mutation Mutations::Boards::Lists::Create
......
---
title: Create an issue board via GraphQL mutation
merge_request: 44298
author:
type: added
......@@ -3061,6 +3061,71 @@ type CreateAnnotationPayload {
errors: [String!]!
}
"""
Autogenerated input type of CreateBoard
"""
input CreateBoardInput {
"""
The ID of the user to be assigned to the board.
"""
assigneeId: String
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The group full path the board is associated with.
"""
groupPath: ID
"""
The IDs of labels to be added to the board.
"""
labelIds: [ID!]
"""
The ID of the milestone to be assigned to the board.
"""
milestoneId: ID
"""
The board name.
"""
name: String
"""
The project full path the board is associated with.
"""
projectPath: ID
"""
The weight of the board.
"""
weight: Boolean
}
"""
Autogenerated return type of CreateBoard
"""
type CreateBoardPayload {
"""
The board after mutation.
"""
board: Board
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
}
"""
Autogenerated input type of CreateBranch
"""
......@@ -11766,6 +11831,7 @@ type Mutation {
configureSast(input: ConfigureSastInput!): ConfigureSastPayload
createAlertIssue(input: CreateAlertIssueInput!): CreateAlertIssuePayload
createAnnotation(input: CreateAnnotationInput!): CreateAnnotationPayload
createBoard(input: CreateBoardInput!): CreateBoardPayload
createBranch(input: CreateBranchInput!): CreateBranchPayload
createClusterAgent(input: CreateClusterAgentInput!): CreateClusterAgentPayload
createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload
......
......@@ -8208,6 +8208,172 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "CreateBoardInput",
"description": "Autogenerated input type of CreateBoard",
"fields": null,
"inputFields": [
{
"name": "projectPath",
"description": "The project full path the board is associated with.",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "groupPath",
"description": "The group full path the board is associated with.",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "name",
"description": "The board name.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "assigneeId",
"description": "The ID of the user to be assigned to the board.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "milestoneId",
"description": "The ID of the milestone to be assigned to the board.",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "weight",
"description": "The weight of the board.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": null
},
{
"name": "labelIds",
"description": "The IDs of labels to be added to the board.",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"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": "CreateBoardPayload",
"description": "Autogenerated return type of CreateBoard",
"fields": [
{
"name": "board",
"description": "The board after mutation.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Board",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"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
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "CreateBranchInput",
......@@ -32820,6 +32986,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createBoard",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "CreateBoardInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "CreateBoardPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createBranch",
"description": null,
......@@ -472,6 +472,16 @@ Autogenerated return type of CreateAnnotation.
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
### CreateBoardPayload
Autogenerated return type of CreateBoard.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `board` | Board | The board after mutation. |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
### CreateBranchPayload
Autogenerated return type of CreateBranch.
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Boards::Create do
let_it_be(:parent) { create(:group) }
let(:group_path) { parent.full_path }
let(:params) do
{
group_path: group_path,
name: name
}
end
it_behaves_like 'boards create mutation'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Boards::Create do
let_it_be(:parent) { create(:project) }
let(:project_path) { parent.full_path }
let(:params) do
{
project_path: project_path,
name: name
}
end
it_behaves_like 'boards create mutation'
end
# frozen_string_literal: true
RSpec.shared_examples 'boards create mutation' do
include GraphqlHelpers
let_it_be(:current_user, reload: true) { create(:user) }
let(:name) { 'board name' }
let(:mutation) { graphql_mutation(:create_board, params) }
subject { post_graphql_mutation(mutation, current_user: current_user) }
def mutation_response
graphql_mutation_response(:create_board)
end
context 'when the user does not have permission' do
it_behaves_like 'a mutation that returns a top-level access error'
it 'does not create the board' do
expect { subject }.not_to change { Board.count }
end
end
context 'when the user has permission' do
before do
parent.add_maintainer(current_user)
end
context 'when the parent (project_path or group_path) param is given' do
context 'when everything is ok' do
it 'creates the board' do
expect { subject }.to change { Board.count }.from(0).to(1)
end
it 'returns the created board' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response).to have_key('board')
expect(mutation_response['board']['name']).to eq(name)
end
end
context 'when the Boards::CreateService returns an error response' do
before do
allow_next_instance_of(Boards::CreateService) do |service|
allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'There was an error.'))
end
end
it 'does not create a board' do
expect { subject }.not_to change { Board.count }
end
it 'returns an error' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response).to have_key('board')
expect(mutation_response['board']).to be_nil
expect(mutation_response['errors'].first).to eq('There was an error.')
end
end
end
context 'when neither project_path nor group_path param is given' do
let(:params) { { name: name } }
it_behaves_like 'a mutation that returns top-level errors',
errors: ['group_path or project_path arguments are required']
it 'does not create the board' do
expect { subject }.not_to change { Board.count }
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