Commit a08c3d65 authored by Jan Provaznik's avatar Jan Provaznik Committed by Nick Thomas

Add graphql mutation for epic

Allows to update epics through GraphQL API.
parent d8f23f51
# frozen_string_literal: true
module Mutations
module ResolvesGroup
extend ActiveSupport::Concern
def resolve_group(full_path:)
resolver.resolve(full_path: full_path)
end
def resolver
Resolvers::GroupResolver.new(object: nil, context: context)
end
end
end
---
title: Add support for epic update through GraphQL API.
merge_request: 18440
author:
type: added
......@@ -736,6 +736,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `flatPath` | String! | |
| `webUrl` | String | |
### UpdateEpicPayload
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
| `epic` | Epic | The epic after mutation |
### UpdateNotePayload
| Name | Type | Description |
......
......@@ -9,6 +9,7 @@ module EE
mount_mutation ::Mutations::DesignManagement::Upload, calls_gitaly: true
mount_mutation ::Mutations::DesignManagement::Delete, calls_gitaly: true
mount_mutation ::Mutations::EpicTree::Reorder
mount_mutation ::Mutations::Epics::Update
end
end
end
......
......@@ -21,7 +21,7 @@ module Mutations
params = args[:moved]
moving_params = params.to_hash.slice(:adjacent_reference_id, :relative_position).merge(base_epic_id: args[:base_epic_id])
result = Epics::TreeReorderService.new(current_user, params[:id], moving_params).execute
result = ::Epics::TreeReorderService.new(current_user, params[:id], moving_params).execute
errors = result[:status] == :error ? [result[:message]] : []
{ errors: errors }
......
# frozen_string_literal: true
module Mutations
module Epics
class Update < BaseMutation
include Mutations::ResolvesGroup
graphql_name 'UpdateEpic'
argument :group_path, GraphQL::ID_TYPE,
required: true,
description: "The group the epic to mutate is in"
argument :iid, GraphQL::STRING_TYPE,
required: true,
description: "The iid of the epic to mutate"
argument :title,
GraphQL::STRING_TYPE,
required: false,
description: 'The title of the epic'
argument :description,
GraphQL::STRING_TYPE,
required: false,
description: 'The description of the epic'
argument :start_date_fixed,
GraphQL::STRING_TYPE,
required: false,
description: 'The start date of the epic'
argument :due_date_fixed,
GraphQL::STRING_TYPE,
required: false,
description: 'The end date of the epic'
argument :start_date_is_fixed,
GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Indicates start date should be sourced from start_date_fixed field not the issue milestones'
argument :due_date_is_fixed,
GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Indicates end date should be sourced from due_date_fixed field not the issue milestones'
argument :state_event,
Types::EpicStateEventEnum,
required: false,
description: 'State event for the epic'
field :epic,
Types::EpicType,
null: true,
description: 'The epic after mutation'
authorize :admin_epic
def resolve(args)
group_path = args.delete(:group_path)
epic_iid = args.delete(:iid)
if args.empty?
raise Gitlab::Graphql::Errors::ArgumentError,
"The list of attributes to update is empty"
end
epic = authorized_find!(group_path: group_path, iid: epic_iid)
epic = ::Epics::UpdateService.new(epic.group, current_user, args).execute(epic)
{
epic: epic.reset,
errors: errors_on_object(epic)
}
end
private
def find_object(group_path:, iid:)
group = resolve_group(full_path: group_path)
resolver = Resolvers::EpicResolver
.single.new(object: group, context: context)
resolver.resolve(iid: iid)
end
end
end
end
# frozen_string_literal: true
module Types
class EpicStateEventEnum < BaseEnum
graphql_name 'EpicStateEvent'
description 'State event of a GitLab Epic'
value 'REOPEN', value: 'reopen', description: 'Reopen the Epic'
value 'CLOSE', value: 'close', description: 'Close the Epic'
end
end
# frozen_string_literal: true
require 'spec_helper'
describe 'Updating an Epic' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group) }
let(:epic) { create(:epic, group: group, title: 'original title') }
let(:attributes) do
{
title: 'updated title',
description: 'some description',
start_date_fixed: '2019-09-17',
due_date_fixed: '2019-09-18',
start_date_is_fixed: true,
due_date_is_fixed: true
}
end
let(:mutation) do
params = { group_path: group.full_path, iid: epic.iid.to_s }.merge(attributes)
graphql_mutation(:update_epic, params)
end
def mutation_response
graphql_mutation_response(:update_epic)
end
context 'when the user does not have permission' do
before do
stub_licensed_features(epics: true)
end
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']
it 'does not update the epic' do
post_graphql_mutation(mutation, current_user: current_user)
expect(epic.reload.title).to eq('original title')
end
end
context 'when the user has permission' do
before do
epic.group.add_developer(current_user)
end
context 'when epics are disabled' do
before do
stub_licensed_features(epics: false)
end
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
context 'when epics are enabled' do
before do
stub_licensed_features(epics: true)
end
it 'updates the epic' do
post_graphql_mutation(mutation, current_user: current_user)
epic_hash = mutation_response['epic']
expect(epic_hash['title']).to eq('updated title')
expect(epic_hash['description']).to eq('some description')
expect(epic_hash['startDateFixed']).to eq('2019-09-17')
expect(epic_hash['startDateIsFixed']).to eq(true)
expect(epic_hash['dueDateFixed']).to eq('2019-09-18')
expect(epic_hash['dueDateIsFixed']).to eq(true)
end
context 'when closing the epic' do
let(:attributes) { { state_event: 'CLOSE' } }
it 'closes open epic' do
post_graphql_mutation(mutation, current_user: current_user)
expect(epic.reload).to be_closed
end
end
context 'when reopening the epic' do
let(:attributes) { { state_event: 'REOPEN' } }
it 'allows epic to be reopend' do
epic.update!(state: 'closed')
post_graphql_mutation(mutation, current_user: current_user)
expect(epic.reload).to be_open
end
end
context 'when there are ActiveRecord validation errors' do
let(:attributes) { { title: '' } }
it_behaves_like 'a mutation that returns errors in the response',
errors: ["Title can't be blank"]
it 'does not update the epic' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['epic']['title']).to eq('original title')
end
end
context 'when the list of attributes is empty' do
let(:attributes) { {} }
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The list of attributes to update is empty']
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::ResolvesGroup do
let(:mutation_class) do
Class.new(Mutations::BaseMutation) do
include Mutations::ResolvesGroup
end
end
let(:context) { double }
subject(:mutation) { mutation_class.new(object: nil, context: context) }
it 'uses the GroupsResolver to resolve groups by path' do
group = create(:group)
expect(Resolvers::GroupResolver).to receive(:new).with(object: nil, context: context).and_call_original
expect(mutation.resolve_group(full_path: group.full_path).sync).to eq(group)
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