Commit 07c16be9 authored by Oswaldo Ferreira's avatar Oswaldo Ferreira

Merge branch '229764-graphql-reorder-lists-within-a-board' into 'master'

GraphQL mutation for updating board lists

Closes #229764

See merge request gitlab-org/gitlab!38942
parents c92bb46d ba7c06d2
# frozen_string_literal: true
module Mutations
module Boards
module Lists
class Update < BaseMutation
graphql_name 'UpdateBoardList'
argument :list_id, GraphQL::ID_TYPE,
required: true,
loads: Types::BoardListType,
description: 'Global ID of the list.'
argument :position, GraphQL::INT_TYPE,
required: false,
description: 'Position of list within the board'
argument :collapsed, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Indicates if list is collapsed for this user'
field :list,
Types::BoardListType,
null: true,
description: 'Mutated list'
def resolve(list: nil, **args)
raise_resource_not_available_error! unless can_read_list?(list)
update_result = update_list(list, args)
{
list: update_result[:list],
errors: list.errors.full_messages
}
end
private
def update_list(list, args)
service = ::Boards::Lists::UpdateService.new(list.board, current_user, args)
service.execute(list)
end
def can_read_list?(list)
return false unless list.present?
Ability.allowed?(current_user, :read_list, list.board)
end
end
end
end
end
......@@ -16,6 +16,7 @@ module Types
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::Boards::Issues::IssueMoveList
mount_mutation Mutations::Boards::Lists::Create
mount_mutation Mutations::Boards::Lists::Update
mount_mutation Mutations::Branches::Create, calls_gitaly: true
mount_mutation Mutations::Commits::Create, calls_gitaly: true
mount_mutation Mutations::Discussions::ToggleResolve
......
---
title: Add GraphQL mutation for updating board list position and collapsed/expanded state.
merge_request: 38942
author:
type: added
......@@ -9576,6 +9576,7 @@ type Mutation {
toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload @deprecated(reason: "Use awardEmojiToggle. Deprecated in 13.2")
updateAlertStatus(input: UpdateAlertStatusInput!): UpdateAlertStatusPayload
updateBoard(input: UpdateBoardInput!): UpdateBoardPayload
updateBoardList(input: UpdateBoardListInput!): UpdateBoardListPayload
updateContainerExpirationPolicy(input: UpdateContainerExpirationPolicyInput!): UpdateContainerExpirationPolicyPayload
updateEpic(input: UpdateEpicInput!): UpdateEpicPayload
......@@ -15588,6 +15589,51 @@ input UpdateBoardInput {
weight: Int
}
"""
Autogenerated input type of UpdateBoardList
"""
input UpdateBoardListInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Indicates if list is collapsed for this user
"""
collapsed: Boolean
"""
Global ID of the list.
"""
listId: ID!
"""
Position of list within the board
"""
position: Int
}
"""
Autogenerated return type of UpdateBoardList
"""
type UpdateBoardListPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
Mutated list
"""
list: BoardList
}
"""
Autogenerated return type of UpdateBoard
"""
......
......@@ -28441,6 +28441,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updateBoardList",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "UpdateBoardListInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "UpdateBoardListPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updateContainerExpirationPolicy",
"description": null,
......@@ -45895,6 +45922,128 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "UpdateBoardListInput",
"description": "Autogenerated input type of UpdateBoardList",
"fields": null,
"inputFields": [
{
"name": "listId",
"description": "Global ID of the list.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "position",
"description": "Position of list within the board",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "collapsed",
"description": "Indicates if list is collapsed for this user",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"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": "UpdateBoardListPayload",
"description": "Autogenerated return type of UpdateBoardList",
"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": "Mutated list",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "BoardList",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "UpdateBoardPayload",
......@@ -2308,6 +2308,16 @@ Autogenerated return type of UpdateAlertStatus
| `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## UpdateBoardListPayload
Autogenerated return type of UpdateBoardList
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `list` | BoardList | Mutated list |
## UpdateBoardPayload
Autogenerated return type of UpdateBoard
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Boards::Lists::Update do
let_it_be(:group) { create(:group, :private) }
let_it_be(:board) { create(:board, group: group) }
let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:list) { create(:list, board: board, position: 0) }
let_it_be(:list2) { create(:list, board: board) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
let(:list_update_params) { { position: 1, collapsed: true } }
before_all do
group.add_reporter(reporter)
group.add_guest(guest)
list.update_preferences_for(reporter, collapsed: false)
end
subject { mutation.resolve(list: list, **list_update_params) }
describe '#resolve' do
context 'with permission to admin board lists' do
let(:current_user) { reporter }
it 'updates the list position and collapsed state as expected' do
subject
reloaded_list = list.reload
expect(reloaded_list.position).to eq(1)
expect(reloaded_list.collapsed?(current_user)).to eq(true)
end
end
context 'with permission to read board lists' do
let(:current_user) { guest }
it 'updates the list collapsed state but not the list position' do
subject
reloaded_list = list.reload
expect(reloaded_list.position).to eq(0)
expect(reloaded_list.collapsed?(current_user)).to eq(true)
end
end
context 'without permission to read board lists' do
let(:current_user) { create(:user) }
it 'raises Resource Not Found error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Update of an existing board list' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
let_it_be(:board) { create(:board, group: group) }
let_it_be(:list) { create(:list, board: board, position: 0) }
let_it_be(:list2) { create(:list, board: board) }
let_it_be(:input) { { list_id: list.to_global_id.to_s, position: 1, collapsed: true } }
let(:mutation) { graphql_mutation(:update_board_list, input) }
let(:mutation_response) { graphql_mutation_response(:update_board_list) }
context 'the user is not allowed to read board lists' do
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
end
before do
list.update_preferences_for(current_user, collapsed: false)
end
context 'when user has permissions to admin board lists' do
before do
group.add_reporter(current_user)
end
it 'updates the list position and collapsed state' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['list']).to include(
'position' => 1,
'collapsed' => true
)
end
end
context 'when user has permissions to read board lists' do
before do
group.add_guest(current_user)
end
it 'updates the list collapsed state but not the list position' 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,
'collapsed' => true
)
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