Commit 1c82f017 authored by Vitali Tatarintev's avatar Vitali Tatarintev

Merge branch 'sy-edit-escalation-policies' into 'master'

Add mutation to edit escalation policies

See merge request gitlab-org/gitlab!62533
parents 2621790e 5e40a799
......@@ -2154,6 +2154,28 @@ Input type: `EscalationPolicyDestroyInput`
| <a id="mutationescalationpolicydestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationescalationpolicydestroyescalationpolicy"></a>`escalationPolicy` | [`EscalationPolicyType`](#escalationpolicytype) | The escalation policy. |
### `Mutation.escalationPolicyUpdate`
Input type: `EscalationPolicyUpdateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationescalationpolicyupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationescalationpolicyupdatedescription"></a>`description` | [`String`](#string) | The description of the escalation policy. |
| <a id="mutationescalationpolicyupdateid"></a>`id` | [`IncidentManagementEscalationPolicyID!`](#incidentmanagementescalationpolicyid) | The ID of the on-call schedule to create the on-call rotation in. |
| <a id="mutationescalationpolicyupdatename"></a>`name` | [`String`](#string) | The name of the escalation policy. |
| <a id="mutationescalationpolicyupdaterules"></a>`rules` | [`[EscalationRuleInput!]`](#escalationruleinput) | The steps of the escalation policy. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationescalationpolicyupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationescalationpolicyupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationescalationpolicyupdateescalationpolicy"></a>`escalationPolicy` | [`EscalationPolicyType`](#escalationpolicytype) | The escalation policy. |
### `Mutation.exportRequirements`
Input type: `ExportRequirementsInput`
......
......@@ -77,6 +77,7 @@ module EE
mount_mutation ::Mutations::IncidentManagement::OncallRotation::Update
mount_mutation ::Mutations::IncidentManagement::OncallRotation::Destroy
mount_mutation ::Mutations::IncidentManagement::EscalationPolicy::Create
mount_mutation ::Mutations::IncidentManagement::EscalationPolicy::Update
mount_mutation ::Mutations::IncidentManagement::EscalationPolicy::Destroy
mount_mutation ::Mutations::AppSec::Fuzzing::API::CiConfiguration::Create
......
......@@ -21,7 +21,45 @@ module Mutations
end
def find_object(id:)
GitlabSchema.object_from_id(id, expected_type: ::IncidentManagement::EscalationPolicy)
GitlabSchema.object_from_id(id, expected_type: ::IncidentManagement::EscalationPolicy).sync
end
# Provide more granular error message for feature availability
# ahead of role-based authorization
def authorize!(object)
raise_feature_not_available! if object && !escalation_policies_available?(object)
super
end
def raise_feature_not_available!
raise_resource_not_available_error! 'Escalation policies are not supported for this project'
end
def escalation_policies_available?(policy)
::Gitlab::IncidentManagement.escalation_policies_available?(policy.project)
end
def prepare_rules_attributes(project, args)
return args unless rules = args.delete(:rules)
iids = rules.collect { |rule| rule[:oncall_schedule_iid] }
found_schedules = schedules_for_iids(project, iids)
rules_attributes = rules.map { |rule| prepare_rule(found_schedules, rule.to_h) }
args.merge(rules_attributes: rules_attributes)
end
def prepare_rule(schedules, rule)
iid = rule.delete(:oncall_schedule_iid).to_i
rule.merge(oncall_schedule: schedules[iid])
end
def schedules_for_iids(project, iids)
schedules = ::IncidentManagement::OncallSchedulesFinder.new(current_user, project, iid: iids).execute
schedules.index_by(&:iid)
end
end
end
......
......@@ -3,18 +3,11 @@
module Mutations
module IncidentManagement
module EscalationPolicy
class Create < BaseMutation
class Create < Base
include ResolvesProject
graphql_name 'EscalationPolicyCreate'
authorize :admin_incident_management_escalation_policy
field :escalation_policy,
::Types::IncidentManagement::EscalationPolicyType,
null: true,
description: 'The escalation policy.'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project to create the escalation policy for.'
......@@ -32,9 +25,8 @@ module Mutations
description: 'The steps of the escalation policy.'
def resolve(project_path:, **args)
@project = authorized_find!(project_path: project_path, **args)
args = prepare_rules_attributes(args)
project = authorized_find!(project_path: project_path, **args)
args = prepare_rules_attributes(project, args)
result = ::IncidentManagement::EscalationPolicies::CreateService.new(
project,
......@@ -47,51 +39,12 @@ module Mutations
private
attr_reader :project
def find_object(project_path:, **args)
unless project = resolve_project(full_path: project_path).sync
raise_project_not_found
end
unless ::Gitlab::IncidentManagement.escalation_policies_available?(project)
raise_resource_not_available_error! 'Escalation policies are not supported for this project'
end
project
end
def prepare_rules_attributes(args)
args[:rules_attributes] = args.delete(:rules).map(&:to_h)
iids = args[:rules_attributes].collect { |rule| rule[:oncall_schedule_iid] }
found_schedules = schedules_for_iids(iids)
args[:rules_attributes].each do |rule|
iid = rule.delete(:oncall_schedule_iid).to_i
rule[:oncall_schedule] = found_schedules[iid]
raise Gitlab::Graphql::Errors::ResourceNotAvailable, "The oncall schedule for iid #{iid} could not be found" unless rule[:oncall_schedule]
end
args
end
def schedules_for_iids(iids)
schedules = ::IncidentManagement::OncallSchedulesFinder.new(current_user, project, iid: iids).execute
schedules.index_by(&:iid)
end
def response(result)
{
escalation_policy: result.payload[:escalation_policy],
errors: result.errors
}
resolve_project(full_path: project_path).sync
end
def raise_project_not_found
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'The project could not be found'
def escalation_policies_available?(project)
::Gitlab::IncidentManagement.escalation_policies_available?(project)
end
end
end
......
# frozen_string_literal: true
module Mutations
module IncidentManagement
module EscalationPolicy
class Update < Base
graphql_name 'EscalationPolicyUpdate'
argument :id, ::Types::GlobalIDType[::IncidentManagement::EscalationPolicy],
required: true,
description: 'The ID of the on-call schedule to create the on-call rotation in.'
argument :name, GraphQL::STRING_TYPE,
required: false,
description: 'The name of the escalation policy.'
argument :description, GraphQL::STRING_TYPE,
required: false,
description: 'The description of the escalation policy.'
argument :rules, [Types::IncidentManagement::EscalationRuleInputType],
required: false,
description: 'The steps of the escalation policy.'
def resolve(id:, **args)
policy = authorized_find!(id: id)
args = prepare_rules_attributes(policy.project, args)
response ::IncidentManagement::EscalationPolicies::UpdateService.new(
policy,
current_user,
args
).execute
end
end
end
end
end
......@@ -7,8 +7,8 @@ module IncidentManagement
user&.can?(:admin_incident_management_escalation_policy, project)
end
def available?
::Gitlab::IncidentManagement.escalation_policies_available?(project)
def invalid_schedules?
params[:rules_attributes]&.any? { |attrs| attrs[:oncall_schedule]&.project != project }
end
def error(message)
......@@ -19,8 +19,20 @@ module IncidentManagement
ServiceResponse.success(payload: { escalation_policy: escalation_policy })
end
def error_no_license
error(_('Escalation policies are not supported for this project'))
def error_no_permissions
error(_('You have insufficient permissions to configure escalation policies for this project'))
end
def error_no_rules
error(_('Escalation policies must have at least one rule'))
end
def error_bad_schedules
error(_('All escalations rules must have a schedule in the same project as the policy'))
end
def error_in_save(policy)
error(policy.errors.full_messages.to_sentence)
end
end
end
......
......@@ -9,9 +9,9 @@ module IncidentManagement
# @option params [String] name
# @option params [String] description
# @option params [Array<Hash>] rules_attributes
# @option rules [Integer] oncall_schedule_id
# @option rules [Integer] elapsed_time_seconds
# @option rules [String] status
# @option params[:rules_attributes] [IncidentManagement::OncallSchedule] oncall_schedule
# @option params[:rules_attributes] [Integer] elapsed_time_seconds
# @option params[:rules_attributes] [String] status
def initialize(project, user, params)
@project = project
@user = user
......@@ -19,13 +19,13 @@ module IncidentManagement
end
def execute
return error_no_license unless available?
return error_no_permissions unless allowed?
return error_no_rules if params[:rules_attributes].blank?
return error_bad_schedules if invalid_schedules?
escalation_policy = project.incident_management_escalation_policies.create(params)
return error_in_create(escalation_policy) unless escalation_policy.persisted?
return error_in_save(escalation_policy) unless escalation_policy.persisted?
success(escalation_policy)
end
......@@ -33,18 +33,6 @@ module IncidentManagement
private
attr_reader :project, :user, :params
def error_no_permissions
error(_('You have insufficient permissions to create an escalation policy for this project'))
end
def error_in_create(escalation_policy)
error(escalation_policy.errors.full_messages.to_sentence)
end
def error_no_rules
error(_('A rule must be provided to create an escalation policy'))
end
end
end
end
......@@ -12,23 +12,18 @@ module IncidentManagement
end
def execute
return error_no_license unless available?
return error_no_permissions unless allowed?
if escalation_policy.destroy
success(escalation_policy)
else
error(escalation_policy.errors.full_messages.to_sentence)
error_in_save(escalation_policy)
end
end
private
attr_reader :escalation_policy, :user, :project
def error_no_permissions
error(_('You have insufficient permissions to remove an escalation policy from this project'))
end
end
end
end
# frozen_string_literal: true
module IncidentManagement
module EscalationPolicies
class UpdateService < EscalationPolicies::BaseService
include Gitlab::Utils::StrongMemoize
# @param escalation_policy [IncidentManagement::EscalationPolicy]
# @param user [User]
# @param params [Hash]
# @option params [String] name
# @option params [String] description
# @option params [Array<Hash>] rules_attributes
# The attributes of the full set of
# the policy's expected escalation rules.
# @option params[:rules_attributes] [IncidentManagement::OncallSchedule] oncall_schedule
# @option params[:rules_attributes] [Integer] elapsed_time_seconds
# @option params[:rules_attributes] [String, Integer, Symbol] status
def initialize(escalation_policy, user, params)
@escalation_policy = escalation_policy
@user = user
@params = params
@project = escalation_policy.project
end
def execute
return error_no_permissions unless allowed?
return error_no_rules if empty_rules?
return error_bad_schedules if invalid_schedules?
reconcile_rules!
if escalation_policy.update(params)
success(escalation_policy)
else
error_in_save(escalation_policy)
end
end
private
attr_reader :escalation_policy, :user, :params, :project
def empty_rules?
params[:rules_attributes] && params[:rules_attributes].empty?
end
# Limits rules_attributes to only new records & prepares
# to delete existing rules which are no longer needed
# when the policy is saved.
#
# Context: Rules are managed via `accepts_nested_attributes_for`
# on the IncidentManagement::EscalationPolicy.
# `accepts_nested_attributes_for` requires explicit
# removal of records, so we'll limit `rules_attributes`
# to new records, then rely on `autosave` to actually
# destroy the unwanted rules after marking them for
# deletion.
def reconcile_rules!
return unless rules_attributes = params.delete(:rules_attributes)
params[:rules_attributes] = remove_obsolete_rules(rules_attributes).to_a
end
def remove_obsolete_rules(rules_attrs)
expected_rules = rules_attrs.to_set { |attrs| normalize(::IncidentManagement::EscalationRule.new(**attrs)) }
escalation_policy.rules.each_with_object(expected_rules) do |existing_rule, new_rules|
# Exclude an expected rule which already corresponds to a persisted record - it's a no-op.
next if new_rules.delete?(normalize(existing_rule))
# Destroy a persisted record, since we don't expect this rule to be on the policy anymore.
existing_rule.mark_for_destruction
end
end
def normalize(rule)
rule.slice(:oncall_schedule_id, :elapsed_time_seconds, :status)
end
end
end
end
......@@ -36,7 +36,10 @@ RSpec.describe Mutations::IncidentManagement::EscalationPolicy::Create do
shared_examples 'raises a resource not available error' do |error|
specify do
expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable, error)
expect { resolve }.to raise_error(
Gitlab::Graphql::Errors::ResourceNotAvailable,
error || Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
)
end
end
......@@ -79,7 +82,7 @@ RSpec.describe Mutations::IncidentManagement::EscalationPolicy::Create do
args[:rules] = []
end
it_behaves_like 'returns a GraphQL error', "A rule must be provided to create an escalation policy"
it_behaves_like 'returns a GraphQL error', 'Escalation policies must have at least one rule'
end
context 'schedule that does not belong to the project' do
......@@ -89,14 +92,14 @@ RSpec.describe Mutations::IncidentManagement::EscalationPolicy::Create do
args[:rules][0][:oncall_schedule_iid] = other_schedule.iid
end
it_behaves_like 'raises a resource not available error', 'The oncall schedule for iid 2 could not be found'
it_behaves_like 'returns a GraphQL error', 'All escalations rules must have a schedule in the same project as the policy'
context 'user does not have permission for project' do
before do
project.add_reporter(current_user)
end
it_behaves_like 'raises a resource not available error', "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
it_behaves_like 'raises a resource not available error'
end
end
end
......@@ -106,7 +109,7 @@ RSpec.describe Mutations::IncidentManagement::EscalationPolicy::Create do
project.add_reporter(current_user)
end
it_behaves_like 'raises a resource not available error', "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
it_behaves_like 'raises a resource not available error'
end
end
......@@ -115,7 +118,7 @@ RSpec.describe Mutations::IncidentManagement::EscalationPolicy::Create do
args[:project_path] = 'something/incorrect'
end
it_behaves_like 'raises a resource not available error', 'The project could not be found'
it_behaves_like 'raises a resource not available error'
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::IncidentManagement::EscalationPolicy::Update do
let_it_be(:maintainer) { create(:user) }
let_it_be(:reporter) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:oncall_schedule) { create(:incident_management_oncall_schedule, project: project) }
let_it_be_with_reload(:escalation_policy) { create(:incident_management_escalation_policy, project: project, rule_count: 2) }
let_it_be_with_reload(:escalation_rules) { escalation_policy.rules }
let_it_be_with_reload(:first_rule) { escalation_rules.first }
let(:args) do
{
id: policy_id,
name: name,
rules: rule_args,
description: 'Updated escalation policy description'
}
end
let(:policy_id) { GitlabSchema.id_from_object(escalation_policy).to_s }
let(:name) { 'Updated escalation policy name' }
let(:rule_args) { nil }
let(:expected_rules) { escalation_rules }
before do
project.add_maintainer(maintainer)
project.add_reporter(reporter)
stub_licensed_features(oncall_schedules: true, escalation_policies: true)
stub_feature_flags(escalation_policies_mvc: project)
end
describe '#resolve' do
let(:current_user) { maintainer }
subject(:resolve) { mutation_for(current_user).resolve(**args) }
# Requires `expected_rules` to be defined
shared_examples 'successful update with no errors' do
it 'returns the updated escalation policy' do
expect(resolve).to match(
escalation_policy: escalation_policy,
errors: be_empty
)
expect(resolve[:escalation_policy]).to have_attributes(escalation_policy.reload.attributes)
expect(escalation_policy).to have_attributes(args.slice(:name, :description))
expect(escalation_policy.rules).to match_array(expected_rules)
end
end
shared_examples 'failed update with a top-level access error' do |error|
specify do
expect { resolve }.to raise_error(
Gitlab::Graphql::Errors::ResourceNotAvailable,
error || Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
)
end
end
context 'when the policy cannot be found' do
let(:policy_id) { Gitlab::GlobalId.build(nil, model_name: ::IncidentManagement::EscalationPolicy.name, id: non_existing_record_id).to_s }
it_behaves_like 'failed update with a top-level access error'
end
context 'when project does not have feature' do
before do
stub_licensed_features(oncall_schedules: true, escalation_policies: false)
end
it_behaves_like 'failed update with a top-level access error', 'Escalation policies are not supported for this project'
end
context 'when user does not have permissions to update the policy' do
let(:current_user) { reporter }
it_behaves_like 'failed update with a top-level access error'
end
context 'when there is an error in updating' do
let(:name) { 'name' * 100 }
it 'returns errors in the body of the response' do
expect(resolve).to eq(
escalation_policy: nil,
errors: ['Name is too long (maximum is 72 characters)']
)
end
end
context 'when rules are excluded' do
let(:rule_args) { nil }
it_behaves_like 'successful update with no errors'
end
context 'when rules are included but empty' do
let(:rule_args) { [] }
it 'returns errors in the body of the response' do
expect(resolve).to eq(
escalation_policy: nil,
errors: ['Escalation policies must have at least one rule']
)
end
end
context 'with rule updates' do
let(:oncall_schedule_iid) { oncall_schedule.iid }
let(:rule_args) do
[
{
oncall_schedule_iid: first_rule.oncall_schedule.iid,
elapsed_time_seconds: first_rule.elapsed_time_seconds,
status: first_rule.status.to_sym
},
{
oncall_schedule_iid: oncall_schedule_iid,
elapsed_time_seconds: 800,
status: :acknowledged
}
]
end
let(:expected_rules) do
[
first_rule,
have_attributes(oncall_schedule_id: oncall_schedule.id, elapsed_time_seconds: 800, status: 'acknowledged')
]
end
it_behaves_like 'successful update with no errors'
context 'when schedule does not exist' do
let(:error_message) { eq("The oncall schedule for iid #{non_existing_record_iid} could not be found") }
let(:oncall_schedule_iid) { non_existing_record_iid }
it 'returns errors in the body of the response' do
expect(resolve).to eq(
escalation_policy: nil,
errors: ['All escalations rules must have a schedule in the same project as the policy']
)
end
context 'the user does not have permission to update policies regardless' do
let(:current_user) { reporter }
it_behaves_like 'failed update with a top-level access error'
end
end
end
end
private
def mutation_for(user)
described_class.new(object: nil, context: { current_user: user }, field: nil)
end
end
......@@ -75,7 +75,7 @@ RSpec.describe 'creating escalation policy' do
it 'raises an error' do
resolve
expect(mutation_response['errors'][0]).to eq("A rule must be provided to create an escalation policy")
expect(mutation_response['errors'][0]).to eq('Escalation policies must have at least one rule')
end
end
......@@ -87,7 +87,7 @@ RSpec.describe 'creating escalation policy' do
it 'raises an error' do
resolve
expect_graphql_errors_to_include("Escalation policies are not supported for this project")
expect_graphql_errors_to_include('Escalation policies are not supported for this project')
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Updating an escalation policy' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:escalation_policy) { create(:incident_management_escalation_policy, project: project) }
let_it_be(:schedule) { escalation_policy.rules.first.oncall_schedule }
let(:variables) do
{
id: escalation_policy.to_global_id.to_s,
name: 'Updated Policy Name',
description: 'Updated Description',
rules: [rule_variables]
}
end
let(:rule_variables) do
{
oncallScheduleIid: schedule.iid,
elapsedTimeSeconds: 60,
status: 'ACKNOWLEDGED'
}
end
let(:mutation) do
graphql_mutation(:escalation_policy_update, variables) do
<<~QL
errors
escalationPolicy {
id
name
description
rules {
status
elapsedTimeSeconds
oncallSchedule { iid }
}
}
QL
end
end
let(:mutation_response) { graphql_mutation_response(:escalation_policy_update) }
before do
stub_licensed_features(oncall_schedules: true, escalation_policies: true)
stub_feature_flags(escalation_policies_mvc: project)
project.add_maintainer(user)
end
it 'updates the escalation policy' do
post_graphql_mutation(mutation, current_user: user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response).to eq(
'errors' => [],
'escalationPolicy' => {
'id' => escalation_policy.to_global_id.to_s,
'name' => variables[:name],
'description' => variables[:description],
'rules' => [{
'status' => rule_variables[:status],
'elapsedTimeSeconds' => rule_variables[:elapsedTimeSeconds],
'oncallSchedule' => { 'iid' => schedule.iid.to_s }
}]
}
)
expect(escalation_policy.reload).to have_attributes(
name: variables[:name],
description: variables[:description],
rules: [
have_attributes(
oncall_schedule: schedule,
status: rule_variables[:status].downcase,
elapsed_time_seconds: rule_variables[:elapsedTimeSeconds]
)
]
)
end
end
......@@ -21,7 +21,7 @@ RSpec.describe IncidentManagement::EscalationPolicies::CreateService do
let(:rule_params) do
[
{
oncall_schedule_id: oncall_schedule.id,
oncall_schedule: oncall_schedule,
elapsed_time_seconds: 60,
status: :resolved
}
......@@ -45,7 +45,7 @@ RSpec.describe IncidentManagement::EscalationPolicies::CreateService do
context 'when user does not have access' do
let(:user) { create(:user) }
it_behaves_like 'error response', 'You have insufficient permissions to create an escalation policy for this project'
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when license is not enabled' do
......@@ -53,7 +53,7 @@ RSpec.describe IncidentManagement::EscalationPolicies::CreateService do
stub_licensed_features(oncall_schedules: true, escalation_policies: false)
end
it_behaves_like 'error response', 'Escalation policies are not supported for this project'
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'validation errors' do
......@@ -68,15 +68,23 @@ RSpec.describe IncidentManagement::EscalationPolicies::CreateService do
context 'no rules are given' do
let(:rule_params) { nil }
it_behaves_like 'error response', 'A rule must be provided to create an escalation policy'
it_behaves_like 'error response', 'Escalation policies must have at least one rule'
end
context 'oncall schedule is blank' do
before do
rule_params[0][:oncall_schedule_id] = nil
rule_params[0][:oncall_schedule] = nil
end
it_behaves_like 'error response', "Rules[0] oncall schedule can't be blank"
it_behaves_like 'error response', 'All escalations rules must have a schedule in the same project as the policy'
end
context 'oncall schedule is on the wrong project' do
before do
rule_params[0][:oncall_schedule] = create(:incident_management_oncall_schedule)
end
it_behaves_like 'error response', 'All escalations rules must have a schedule in the same project as the policy'
end
context 'project has an existing escalation policy' do
......
......@@ -34,13 +34,13 @@ RSpec.describe IncidentManagement::EscalationPolicies::DestroyService do
context 'when the current_user is anonymous' do
let(:current_user) { nil }
it_behaves_like 'error response', 'You have insufficient permissions to remove an escalation policy from this project'
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when the current_user does not have permissions to remove escalation policies' do
let(:current_user) { user_without_permissions }
it_behaves_like 'error response', 'You have insufficient permissions to remove an escalation policy from this project'
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when license is not enabled' do
......@@ -48,7 +48,7 @@ RSpec.describe IncidentManagement::EscalationPolicies::DestroyService do
stub_licensed_features(oncall_schedules: true, escalation_policies: false)
end
it_behaves_like 'error response', 'Escalation policies are not supported for this project'
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when feature is not available' do
......@@ -56,7 +56,7 @@ RSpec.describe IncidentManagement::EscalationPolicies::DestroyService do
stub_feature_flags(escalation_policies_mvc: false)
end
it_behaves_like 'error response', 'Escalation policies are not supported for this project'
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when an error occurs during removal' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::EscalationPolicies::UpdateService do
let_it_be(:user_with_permissions) { create(:user) }
let_it_be(:user_without_permissions) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:oncall_schedule) { create(:incident_management_oncall_schedule, project: project) }
let_it_be_with_reload(:escalation_policy) { create(:incident_management_escalation_policy, project: project, rule_count: 2) }
let_it_be_with_reload(:escalation_rules) { escalation_policy.rules }
let(:service) { described_class.new(escalation_policy, current_user, params) }
let(:current_user) { user_with_permissions }
let(:params) do
{
name: 'Updated escalation policy name',
description: 'Updated escalation policy description',
rules_attributes: rule_params
}
end
let(:rule_params) { [*existing_rules_params, new_rule_params] }
let(:existing_rules_params) do
escalation_rules.map do |rule|
rule.slice(:oncall_schedule, :elapsed_time_seconds)
.merge(status: rule.status.to_sym)
end
end
let(:new_rule_params) do
{
oncall_schedule: oncall_schedule,
elapsed_time_seconds: 800,
status: :acknowledged
}
end
let(:new_rule) { have_attributes(**new_rule_params.except(:status), status: 'acknowledged') }
before do
stub_licensed_features(oncall_schedules: true, escalation_policies: true)
stub_feature_flags(escalation_policies_mvc: project)
end
before_all do
project.add_maintainer(user_with_permissions)
end
describe '#execute' do
shared_examples 'error response' do |message|
it 'has an informative message' do
expect(execute).to be_error
expect(execute.message).to eq(message)
end
end
# Requires `expected_rules` to be defined
shared_examples 'successful update with no errors' do
it 'returns the updated escalation policy' do
expect(execute).to be_success
expect(execute.payload).to eq(escalation_policy: escalation_policy.reload)
expect(escalation_policy).to have_attributes(params.slice(:name, :description))
expect(escalation_policy.rules).to match_array(expected_rules)
end
end
subject(:execute) { service.execute }
context 'when the current_user is anonymous' do
let(:current_user) { nil }
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when the current_user does not have permissions to update escalation policies' do
let(:current_user) { user_without_permissions }
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when license is not enabled' do
before do
stub_licensed_features(oncall_schedules: true, escalation_policies: false)
end
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when feature is not available' do
before do
stub_feature_flags(escalation_policies_mvc: false)
end
it_behaves_like 'error response', 'You have insufficient permissions to configure escalation policies for this project'
end
context 'when only new rules are added' do
let(:expected_rules) { [*escalation_rules, new_rule] }
it_behaves_like 'successful update with no errors'
end
context 'when all old rules are replaced' do
let(:rule_params) { [new_rule_params] }
let(:expected_rules) { [new_rule] }
it_behaves_like 'successful update with no errors'
end
context 'when some rules are preserved, added, and deleted' do
let(:rule_params) { [existing_rules_params.first, new_rule_params] }
let(:expected_rules) { [escalation_rules.first, new_rule] }
it_behaves_like 'successful update with no errors'
end
context 'when rules are unchanged' do
let(:rule_params) { existing_rules_params }
let(:expected_rules) { escalation_rules }
it_behaves_like 'successful update with no errors'
end
context 'when rules are excluded' do
let(:expected_rules) { escalation_rules }
before do
params.delete(:rules_attributes)
end
it_behaves_like 'successful update with no errors'
end
context 'when rules are explicitly nil' do
let(:rule_params) { nil }
let(:expected_rules) { escalation_rules }
it_behaves_like 'successful update with no errors'
end
context 'when rules are explicitly empty' do
let(:rule_params) { [] }
let(:expected_rules) { escalation_rules }
it_behaves_like 'error response', 'Escalation policies must have at least one rule'
end
context 'when the on-call schedule is not present on the rule' do
let(:rule_params) { [new_rule_params.except(:oncall_schedule)] }
it_behaves_like 'error response', 'All escalations rules must have a schedule in the same project as the policy'
end
context 'when the on-call schedule is not on the project' do
let(:other_schedule) { create(:incident_management_oncall_schedule) }
let(:rule_params) { [new_rule_params.merge(oncall_schedule: other_schedule)] }
it_behaves_like 'error response', 'All escalations rules must have a schedule in the same project as the policy'
end
context 'when an error occurs during update' do
before do
params[:name] = ''
end
it_behaves_like 'error response', "Name can't be blank"
end
end
end
......@@ -1474,9 +1474,6 @@ msgstr ""
msgid "A rebase is already in progress."
msgstr ""
msgid "A rule must be provided to create an escalation policy"
msgstr ""
msgid "A secure token that identifies an external storage request."
msgstr ""
......@@ -3228,6 +3225,9 @@ msgstr ""
msgid "All epics"
msgstr ""
msgid "All escalations rules must have a schedule in the same project as the policy"
msgstr ""
msgid "All groups and projects"
msgstr ""
......@@ -13039,7 +13039,7 @@ msgstr ""
msgid "Escalation policies"
msgstr ""
msgid "Escalation policies are not supported for this project"
msgid "Escalation policies must have at least one rule"
msgstr ""
msgid "EscalationPolicies|+ Add an additional rule"
......@@ -37485,21 +37485,18 @@ msgstr ""
msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
msgstr ""
msgid "You have insufficient permissions to create a Todo for this alert"
msgid "You have insufficient permissions to configure escalation policies for this project"
msgstr ""
msgid "You have insufficient permissions to create an HTTP integration for this project"
msgid "You have insufficient permissions to create a Todo for this alert"
msgstr ""
msgid "You have insufficient permissions to create an escalation policy for this project"
msgid "You have insufficient permissions to create an HTTP integration for this project"
msgstr ""
msgid "You have insufficient permissions to create an on-call schedule for this project"
msgstr ""
msgid "You have insufficient permissions to remove an escalation policy from this project"
msgstr ""
msgid "You have insufficient permissions to remove an on-call rotation from this project"
msgstr ""
......
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