Commit db447fd8 authored by Michael Kozono's avatar Michael Kozono

Merge branch 'bw-board-query-by-id' into 'master'

GraphQL: Allow Group/Project Board to be queried by ID

See merge request gitlab-org/gitlab!24825
parents 88f21ac2 c7c88480
......@@ -4,7 +4,11 @@ module Resolvers
class BoardsResolver < BaseResolver
type Types::BoardType, null: true
def resolve(**args)
argument :id, GraphQL::ID_TYPE,
required: false,
description: 'Find a board by its ID'
def resolve(id: nil)
# The project or group could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project/group to query for boards, so
# make sure it's loaded and not `nil` before continuing.
......@@ -12,7 +16,17 @@ module Resolvers
return Board.none unless parent
Boards::ListService.new(parent, context[:current_user]).execute(create_default_board: false)
Boards::ListService.new(parent, context[:current_user], board_id: extract_board_id(id)).execute(create_default_board: false)
rescue ActiveRecord::RecordNotFound
Board.none
end
private
def extract_board_id(gid)
return unless gid.present?
GitlabSchema.parse_gid(gid, expected_type: ::Board).model_id
end
end
end
......@@ -52,6 +52,12 @@ module Types
null: true,
description: 'Boards of the group',
resolver: Resolvers::BoardsResolver
field :board,
Types::BoardType,
null: true,
description: 'A single board of the group',
resolver: Resolvers::BoardsResolver.single
end
end
......
......@@ -185,6 +185,12 @@ module Types
null: true,
description: 'Boards of the project',
resolver: Resolvers::BoardsResolver
field :board,
Types::BoardType,
null: true,
description: 'A single board of the project',
resolver: Resolvers::BoardsResolver.single
end
end
......
---
title: Allow group/project board to be queried by ID via GraphQL
merge_request: 24825
author:
type: added
......@@ -2770,6 +2770,16 @@ type Group {
"""
avatarUrl: String
"""
A single board of the group
"""
board(
"""
Find a board by its ID
"""
id: ID
): Board
"""
Boards of the group
"""
......@@ -2789,6 +2799,11 @@ type Group {
"""
first: Int
"""
Find a board by its ID
"""
id: ID
"""
Returns the last _n_ elements from the list.
"""
......@@ -5254,6 +5269,16 @@ type Project {
"""
avatarUrl: String
"""
A single board of the project
"""
board(
"""
Find a board by its ID
"""
id: ID
): Board
"""
Boards of the project
"""
......@@ -5273,6 +5298,11 @@ type Project {
"""
first: Int
"""
Find a board by its ID
"""
id: ID
"""
Returns the last _n_ elements from the list.
"""
......
......@@ -368,10 +368,43 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "board",
"description": "A single board of the project",
"args": [
{
"name": "id",
"description": "Find a board by its ID",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Board",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "boards",
"description": "Boards of the project",
"args": [
{
"name": "id",
"description": "Find a board by its ID",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......@@ -3175,10 +3208,43 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "board",
"description": "A single board of the group",
"args": [
{
"name": "id",
"description": "Find a board by its ID",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Board",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "boards",
"description": "Boards of the group",
"args": [
{
"name": "id",
"description": "Find a board by its ID",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......
......@@ -426,6 +426,7 @@ Autogenerated return type of EpicTreeReorder
| --- | ---- | ---------- |
| `autoDevopsEnabled` | Boolean | Indicates whether Auto DevOps is enabled for all projects within this group |
| `avatarUrl` | String | Avatar URL of the group |
| `board` | Board | A single board of the group |
| `description` | String | Description of the namespace |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `emailsDisabled` | Boolean | Indicates if a group has email notifications disabled |
......@@ -801,6 +802,7 @@ Information about pagination in a connection.
| `archived` | Boolean | Indicates the archived status of the project |
| `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically |
| `avatarUrl` | String | URL to avatar image file of the project |
| `board` | Board | A single board of the project |
| `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry |
| `createdAt` | Time | Timestamp of the project creation |
| `description` | String | Short description of the project |
......
......@@ -13,7 +13,6 @@ describe 'get list of boards' do
describe 'for a group' do
let(:board_parent) { create(:group, :private) }
let(:boards_data) { graphql_data['group']['boards']['edges'] }
it_behaves_like 'group and project boards query'
end
......
......@@ -45,6 +45,21 @@ describe Resolvers::BoardsResolver do
expect(resolve_boards).to eq [board1]
end
end
context 'when querying for a single board' do
let(:board1) { create(:board, name: 'One', resource_parent: board_parent) }
it 'returns specified board' do
expect(resolve_boards(args: { id: global_id_of(board1) })).to eq [board1]
end
it 'returns nil if board not found' do
outside_parent = create(board_parent.class.underscore.to_sym)
outside_board = create(:board, name: 'outside board', resource_parent: outside_parent)
expect(resolve_boards(args: { id: global_id_of(outside_board) })).to eq Board.none
end
end
end
describe '#resolve' do
......
......@@ -9,14 +9,12 @@ describe 'get list of boards' do
describe 'for a project' do
let(:board_parent) { create(:project, :repository, :private) }
let(:boards_data) { graphql_data['project']['boards']['edges'] }
it_behaves_like 'group and project boards query'
end
describe 'for a group' do
let(:board_parent) { create(:group, :private) }
let(:boards_data) { graphql_data['group']['boards']['edges'] }
before do
allow(board_parent).to receive(:multiple_issue_boards_available?).and_return(false)
......
......@@ -5,6 +5,8 @@ RSpec.shared_context 'group and project boards query context' do
let(:current_user) { user }
let(:params) { '' }
let(:board_parent_type) { board_parent.class.to_s.downcase }
let(:boards_data) { graphql_data[board_parent_type]['boards']['edges'] }
let(:board_data) { graphql_data[board_parent_type]['board'] }
let(:start_cursor) { graphql_data[board_parent_type]['boards']['pageInfo']['startCursor'] }
let(:end_cursor) { graphql_data[board_parent_type]['boards']['pageInfo']['endCursor'] }
......@@ -28,6 +30,18 @@ RSpec.shared_context 'group and project boards query context' do
)
end
def query_single_board(board_params = params)
graphql_query_for(
board_parent_type,
{ 'fullPath' => board_parent.full_path },
<<~BOARD
board(#{board_params}) {
#{all_graphql_fields_for('board'.classify)}
}
BOARD
)
end
def grab_names(data = boards_data)
data.map do |board|
board.dig('node', 'name')
......
......@@ -89,4 +89,24 @@ RSpec.shared_examples 'group and project boards query' do
end
end
end
context 'when querying for a single board' do
before do
board_parent.add_reporter(current_user)
end
it_behaves_like 'a working graphql query' do
before do
post_graphql(query_single_board, current_user: current_user)
end
end
it 'finds the correct board' do
board = create(:board, resource_parent: board_parent, name: 'A')
post_graphql(query_single_board("id: \"#{global_id_of(board)}\""), current_user: current_user)
expect(board_data['name']).to eq board.name
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