Commit 99cba2fc authored by Mario Celi's avatar Mario Celi

Add update iteration cadence GraphQL mutation

- Adds iteration cadences update mutation and cadence update service
- Adds specs for update service and requests specs for mutation
- Updates GQL docs
parent d22c6821
......@@ -3593,6 +3593,16 @@ An edge in a connection.
| `cursor` | [`String!`](#string) | A cursor for use in pagination. |
| `node` | [`IterationCadence`](#iterationcadence) | The item at the end of the edge. |
### `IterationCadenceUpdatePayload`
Autogenerated return type of IterationCadenceUpdate.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| `iterationCadence` | [`IterationCadence`](#iterationcadence) | The updated iteration cadence. |
### `IterationConnection`
The connection type for Iteration.
......
......@@ -26,6 +26,7 @@ module EE
mount_mutation ::Mutations::Iterations::Create
mount_mutation ::Mutations::Iterations::Update
mount_mutation ::Mutations::Iterations::Cadences::Create
mount_mutation ::Mutations::Iterations::Cadences::Update
mount_mutation ::Mutations::RequirementsManagement::CreateRequirement
mount_mutation ::Mutations::RequirementsManagement::ExportRequirements
mount_mutation ::Mutations::RequirementsManagement::UpdateRequirement
......
# frozen_string_literal: true
module Mutations
module Iterations
module Cadences
class Update < BaseMutation
graphql_name 'IterationCadenceUpdate'
authorize :admin_iteration_cadence
argument :id, ::Types::GlobalIDType[::Iterations::Cadence], required: true,
description: copy_field_description(Types::Iterations::CadenceType, :id)
argument :title, GraphQL::STRING_TYPE, required: false,
description: copy_field_description(Types::Iterations::CadenceType, :title)
argument :duration_in_weeks, GraphQL::INT_TYPE, required: false,
description: copy_field_description(Types::Iterations::CadenceType, :duration_in_weeks)
argument :iterations_in_advance, GraphQL::INT_TYPE, required: false,
description: copy_field_description(Types::Iterations::CadenceType, :iterations_in_advance)
argument :start_date, Types::TimeType, required: false,
description: copy_field_description(Types::Iterations::CadenceType, :start_date)
argument :automatic, GraphQL::BOOLEAN_TYPE, required: false,
description: copy_field_description(Types::Iterations::CadenceType, :automatic)
argument :active, GraphQL::BOOLEAN_TYPE, required: false,
description: copy_field_description(Types::Iterations::CadenceType, :active)
field :iteration_cadence, Types::Iterations::CadenceType, null: true,
description: 'The updated iteration cadence.'
def resolve(id:, **attrs)
iteration_cadence = authorized_find!(id: id)
response = ::Iterations::Cadences::UpdateService.new(iteration_cadence, current_user, attrs).execute
response_object = response.success? ? response.payload[:iteration_cadence] : nil
{
iteration_cadence: response_object,
errors: response.errors
}
end
private
def find_object(id:)
# TODO: Remove coercion when working on https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::Iterations::Cadence].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
end
end
......@@ -219,6 +219,7 @@ module EE
enable :create_iteration
enable :admin_iteration
enable :create_iteration_cadence
enable :admin_iteration_cadence
end
rule { reporter & epics_available }.policy do
......
# frozen_string_literal: true
module Iterations
module Cadences
class UpdateService
include Gitlab::Allowable
def initialize(iteration_cadence, user, params = {})
@iteration_cadence, @current_user, @params = iteration_cadence, user, params.dup
end
def execute
return ::ServiceResponse.error(message: _('Operation not allowed'), http_status: 403) unless can_update_iteration_cadence?
if iteration_cadence.update(params)
::ServiceResponse.success(payload: { iteration_cadence: iteration_cadence })
else
::ServiceResponse.error(message: iteration_cadence.errors.full_messages, http_status: 422)
end
end
private
attr_reader :iteration_cadence, :params, :current_user
def can_update_iteration_cadence?
group = iteration_cadence.group
group.iteration_cadences_feature_flag_enabled? &&
group.feature_available?(:iterations) &&
can?(current_user, :admin_iteration_cadence, iteration_cadence)
end
end
end
end
......@@ -90,7 +90,7 @@ RSpec.describe GroupPolicy do
stub_licensed_features(iterations: false)
end
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration, :create_iteration_cadence) }
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration, :create_iteration_cadence, :admin_iteration_cadence) }
end
context 'when iterations feature is enabled' do
......@@ -101,14 +101,14 @@ RSpec.describe GroupPolicy do
context 'when user is a developer' do
let(:current_user) { developer }
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration, :read_iteration_cadence, :create_iteration_cadence) }
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration, :read_iteration_cadence, :create_iteration_cadence, :admin_iteration_cadence) }
end
context 'when user is a guest' do
let(:current_user) { guest }
it { is_expected.to be_allowed(:read_iteration, :read_iteration_cadence) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration, :create_iteration_cadence) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration, :create_iteration_cadence, :admin_iteration_cadence) }
end
context 'when user is logged out' do
......@@ -124,7 +124,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { nil }
it { is_expected.to be_allowed(:read_iteration, :read_iteration_cadence) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration, :create_iteration_cadence) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration, :create_iteration_cadence, :admin_iteration_cadence) }
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Updating an iteration cadence' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:iteration_cadence, refind: true) { create(:iterations_cadence, group: group) }
let(:start_date) { Time.now.strftime('%F') }
let(:attributes) do
{
title: 'title',
start_date: start_date,
duration_in_weeks: 1,
iterations_in_advance: 1,
automatic: false,
active: false
}
end
let(:params) do
{ id: iteration_cadence.to_global_id.to_s }
end
let(:mutation) do
graphql_mutation(:iteration_cadence_update, params.merge(attributes))
end
def mutation_response
graphql_mutation_response(:iteration_cadence_update)
end
context 'when the user does not have permission' do
before do
stub_licensed_features(iterations: true)
end
it_behaves_like 'a mutation that returns a top-level access error'
it 'does not update the iteration cadence' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
iteration_cadence.reload
end.to not_change(iteration_cadence, :title)
end
end
context 'when the user has permission' do
before do
group.add_developer(current_user)
end
context 'when iterations feature is disabled' do
before do
stub_licensed_features(iterations: 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 iterations feature is enabled' do
before do
stub_licensed_features(iterations: true)
end
it 'updates the iteration cadence', :aggregate_failures do
post_graphql_mutation(mutation, current_user: current_user)
iteration_cadence_hash = mutation_response['iterationCadence']
expect(iteration_cadence_hash['title']).to eq('title')
expect(iteration_cadence_hash['startDate'].to_date).to eq(start_date.to_date)
expect(iteration_cadence_hash['durationInWeeks']).to eq(1)
expect(iteration_cadence_hash['iterationsInAdvance']).to eq(1)
expect(iteration_cadence_hash['automatic']).to eq(false)
expect(iteration_cadence_hash['active']).to eq(false)
iteration_cadence.reload
expect(iteration_cadence.title).to eq('title')
expect(iteration_cadence.start_date).to eq(start_date.to_date)
expect(iteration_cadence.duration_in_weeks).to eq(1)
expect(iteration_cadence.iterations_in_advance).to eq(1)
expect(iteration_cadence.automatic).to eq(false)
expect(iteration_cadence.active).to eq(false)
end
context 'when iteration_cadences feature flag is disabled' do
before do
stub_feature_flags(iteration_cadences: false)
end
it_behaves_like 'a mutation that returns errors in the response', errors: ["Operation not allowed"]
end
context 'when there are ActiveRecord validation errors' do
let(:attributes) { { id: iteration_cadence.to_global_id.to_s, title: '' } }
it_behaves_like 'a mutation that returns errors in the response',
errors: ["Title can't be blank"]
it 'does not update the iteration cadence' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
iteration_cadence.reload
end.not_to change(iteration_cadence, :title)
end
end
context 'when required arguments are missing' do
let(:params) { {} }
it 'returns error about required argument' do
post_graphql_mutation(mutation, current_user: current_user)
expect_graphql_errors_to_include(/was provided invalid value for id \(Expected value to not be null\)/)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Iterations::Cadences::UpdateService do
subject(:results) { described_class.new(iteration_cadence, user, params).execute }
let_it_be(:group, refind: true) { create(:group) }
let_it_be(:user) { create(:user) }
let_it_be(:iteration_cadence, refind: true) { create(:iterations_cadence, group: group, duration_in_weeks: 1, iterations_in_advance: 2) }
let(:params) do
{
title: 'Updated iteration cadence',
start_date: 2.days.from_now.to_s,
duration_in_weeks: 4,
iterations_in_advance: 5
}
end
RSpec.shared_examples 'cadence update fails with message' do |message:|
it { is_expected.to be_error }
it 'returns not allowed message' do
expect(results.message).to eq(message)
end
it 'does not update cadence values' do
expect do
results
iteration_cadence.reload
end.to not_change(iteration_cadence, :title).and(
not_change(iteration_cadence, :start_date)
).and(
not_change(iteration_cadence, :duration_in_weeks)
).and(
not_change(iteration_cadence, :iterations_in_advance)
)
end
end
describe '#execute' do
context 'when iterations feature enabled' do
before do
stub_licensed_features(iterations: true)
end
context 'when user is authorized' do
before do
group.add_developer(user)
end
it { is_expected.to be_success }
it 'updates cadence values' do
expect do
results
iteration_cadence.reload
end.to change(iteration_cadence, :title).to('Updated iteration cadence').and(
change(iteration_cadence, :start_date)
).and(
change(iteration_cadence, :duration_in_weeks).to(4)
).and(
change(iteration_cadence, :iterations_in_advance).to(5)
)
end
it 'returns the cadence as part of the response' do
expect(results.payload[:iteration_cadence]).to eq(iteration_cadence)
end
context 'when provided invalid params' do
let(:params) { { title: '' } }
it_behaves_like 'cadence update fails with message', message: ["Title can't be blank"]
end
end
context 'when user is not authorized' do
it_behaves_like 'cadence update fails with message', message: 'Operation not allowed'
end
end
context 'when iterations feature disabled' do
before do
stub_licensed_features(iterations: false)
end
context 'when user is authorized' do
before do
group.add_developer(user)
end
it_behaves_like 'cadence update fails with message', message: 'Operation not allowed'
end
context 'when user is not authorized' do
it_behaves_like 'cadence update fails with message', message: 'Operation not allowed'
end
end
context 'when iteration cadences feature flag disabled' do
before do
stub_licensed_features(iterations: true)
stub_feature_flags(iteration_cadences: false)
end
context 'when user is authorized' do
before do
group.add_developer(user)
end
it_behaves_like 'cadence update fails with message', message: 'Operation not allowed'
end
context 'when user is not authorized' do
it_behaves_like 'cadence update fails with message', message: 'Operation not allowed'
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