Commit b2894eed authored by Igor Drozdov's avatar Igor Drozdov

Merge branch '233307-update-and-expose-board-labels' into 'master'

Update and expose board labels in graphql

See merge request gitlab-org/gitlab!44204
parents 7ea32c5d 02b38723
...@@ -1072,6 +1072,31 @@ type Board { ...@@ -1072,6 +1072,31 @@ type Board {
""" """
id: ID! id: ID!
"""
Labels of the board
"""
labels(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): LabelConnection
""" """
Lists of the board Lists of the board
""" """
...@@ -18763,6 +18788,16 @@ input UpdateBoardInput { ...@@ -18763,6 +18788,16 @@ input UpdateBoardInput {
""" """
id: ID! id: ID!
"""
The IDs of labels to be added to the board.
"""
labelIds: [LabelID!]
"""
Labels of the issue
"""
labels: [String!]
""" """
The id of milestone to be assigned to the board. The id of milestone to be assigned to the board.
""" """
......
...@@ -2840,6 +2840,59 @@ ...@@ -2840,6 +2840,59 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "labels",
"description": "Labels of the board",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "LabelConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "lists", "name": "lists",
"description": "Lists of the board", "description": "Lists of the board",
...@@ -54769,6 +54822,42 @@ ...@@ -54769,6 +54822,42 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "labels",
"description": "Labels of the issue",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"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": "LabelID",
"ofType": null
}
}
},
"defaultValue": null
},
{ {
"name": "clientMutationId", "name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.", "description": "A unique identifier for the client performing the mutation.",
...@@ -9,8 +9,10 @@ module EE ...@@ -9,8 +9,10 @@ module EE
field :assignee, type: ::Types::UserType, null: true, field :assignee, type: ::Types::UserType, null: true,
description: 'The board assignee.' description: 'The board assignee.'
field :milestone, type: ::Types::MilestoneType, null: true, field :epics, ::Types::Boards::BoardEpicType.connection_type, null: true,
description: 'The board milestone.' description: 'Epics associated with board issues.',
resolver: ::Resolvers::BoardGroupings::EpicsResolver,
complexity: 5
field :hide_backlog_list, type: GraphQL::BOOLEAN_TYPE, null: true, field :hide_backlog_list, type: GraphQL::BOOLEAN_TYPE, null: true,
description: 'Whether or not backlog list is hidden.' description: 'Whether or not backlog list is hidden.'
...@@ -18,13 +20,14 @@ module EE ...@@ -18,13 +20,14 @@ module EE
field :hide_closed_list, type: GraphQL::BOOLEAN_TYPE, null: true, field :hide_closed_list, type: GraphQL::BOOLEAN_TYPE, null: true,
description: 'Whether or not closed list is hidden.' description: 'Whether or not closed list is hidden.'
field :labels, ::Types::LabelType.connection_type, null: true,
description: 'Labels of the board'
field :milestone, type: ::Types::MilestoneType, null: true,
description: 'The board milestone.'
field :weight, type: GraphQL::INT_TYPE, null: true, field :weight, type: GraphQL::INT_TYPE, null: true,
description: 'Weight of the board.' description: 'Weight of the board.'
field :epics, ::Types::Boards::BoardEpicType.connection_type, null: true,
description: 'Epics associated with board issues.',
resolver: ::Resolvers::BoardGroupings::EpicsResolver,
complexity: 5
end end
end end
end end
......
...@@ -41,6 +41,14 @@ module Mutations ...@@ -41,6 +41,14 @@ module Mutations
required: false, required: false,
description: 'The weight value to be assigned to the board.' description: 'The weight value to be assigned to the board.'
argument :labels, [GraphQL::STRING_TYPE],
required: false,
description: copy_field_description(Types::IssueType, :labels)
argument :label_ids, [::Types::GlobalIDType[::Label]],
required: false,
description: 'The IDs of labels to be added to the board.'
field :board, field :board,
Types::BoardType, Types::BoardType,
null: true, null: true,
...@@ -61,6 +69,15 @@ module Mutations ...@@ -61,6 +69,15 @@ module Mutations
} }
end end
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
private private
def find_object(id:) def find_object(id:)
...@@ -76,8 +93,16 @@ module Mutations ...@@ -76,8 +93,16 @@ module Mutations
args[:milestone_id] = GitlabSchema.parse_gid(args[:milestone_id], expected_type: ::Milestone).model_id args[:milestone_id] = GitlabSchema.parse_gid(args[:milestone_id], expected_type: ::Milestone).model_id
end end
args[:label_ids] &&= args[:label_ids].map do |label_id|
::GitlabSchema.parse_gid(label_id, expected_type: ::Label).model_id
end
args args
end end
def mutually_exclusive_args
[:labels, :label_ids]
end
end end
end end
end end
---
title: Update and expose board labels trough GraphQL API
merge_request: 44204
author:
type: added
...@@ -4,6 +4,8 @@ require 'spec_helper' ...@@ -4,6 +4,8 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Board'] do RSpec.describe GitlabSchema.types['Board'] do
it 'includes the ee specific fields' do it 'includes the ee specific fields' do
expect(described_class).to have_graphql_fields(:weight, :epics).at_least expect(described_class).to have_graphql_fields(
:assignee, :epics, :hide_backlog_list, :hide_closed_list, :labels, :milestone, :weight
).at_least
end end
end end
...@@ -7,26 +7,31 @@ RSpec.describe Mutations::Boards::Update do ...@@ -7,26 +7,31 @@ RSpec.describe Mutations::Boards::Update do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:board) { create(:board, project: project) } let_it_be(:board) { create(:board, project: project) }
let_it_be(:milestone) { create(:milestone, project: project) } let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:label1) { create(:label, project: project) }
let_it_be(:label2) { create(:label, project: project) }
let(:new_labels) { %w(new_label1 new_label2) }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutated_board) { subject[:board] } let(:mutated_board) { subject[:board] }
specify { expect(described_class).to require_graphql_authorizations(:admin_board) } let(:mutation_params) do
{
id: board.to_global_id,
name: 'Test board 1',
hide_backlog_list: true,
hide_closed_list: true,
weight: 3,
assignee_id: user.to_global_id,
milestone_id: milestone.to_global_id,
label_ids: [label1.to_global_id, label2.to_global_id]
}
end
describe '#resolve' do subject { mutation.resolve(mutation_params) }
let(:mutation_params) do
{
id: board.to_global_id,
name: 'Test board 1',
hide_backlog_list: true,
hide_closed_list: true,
weight: 3,
assignee_id: user.to_global_id,
milestone_id: milestone.to_global_id
}
end
subject { mutation.resolve(mutation_params) } specify { expect(described_class).to require_graphql_authorizations(:admin_board) }
describe '#resolve' do
context 'when the user cannot admin the board' do context 'when the user cannot admin the board' do
it 'raises an error' do it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
...@@ -45,13 +50,40 @@ RSpec.describe Mutations::Boards::Update do ...@@ -45,13 +50,40 @@ RSpec.describe Mutations::Boards::Update do
hide_closed_list: true, hide_closed_list: true,
weight: 3, weight: 3,
assignee: user, assignee: user,
milestone: milestone milestone: milestone,
labels: [label1, label2]
} }
subject subject
expect(board.reload).to have_attributes(expected_attributes) expect(board.reload).to have_attributes(expected_attributes)
end end
context 'when passing labels param' do
before do
mutation_params.delete(:label_ids)
mutation_params.merge!(labels: new_labels)
end
it 'updates board with correct labels' do
subject
expect(board.reload.labels.pluck(:title)).to eq(new_labels)
end
end
end
end
describe '#ready' do
context 'when passing both labels & label_ids param' do
before do
mutation_params.merge!(labels: new_labels)
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end end
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