Commit 4bf9fb98 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch '262857-creation-rotation-service' into 'master'

Add service to create Oncall-rotation

See merge request gitlab-org/gitlab!49194
parents 72baaa1a 7e90bed5
# frozen_string_literal: true
module IncidentManagement
class OncallRotationPolicy < ::BasePolicy
delegate :project
end
end
# frozen_string_literal: true
module IncidentManagement
module OncallRotations
class CreateService
MAXIMUM_PARTICIPANTS = 100
# @param schedule [IncidentManagement::OncallSchedule]
# @param project [Project]
# @param current_user [User]
# @param params [Hash<Symbol,Any>]
# @param params - name [String] The name of the on-call rotation.
# @param params - length [Integer] The length of the rotation.
# @param params - length_unit [String] The unit of the rotation length. (One of 'hours', days', 'weeks')
# @param params - starts_at [DateTime] The datetime the rotation starts on.
# @param params - participants [Array<hash>] An array of hashes defining participants of the on-call rotations.
# @option opts - participant [User] The user who is part of the rotation
# @option opts - color_palette [String] The color palette to assign to the on-call user, for example: "blue".
# @option opts - color_weight [String] The color weight to assign to for the on-call user, for example "500". Max 4 chars.
def initialize(schedule, project, current_user, params)
@schedule = schedule
@project = project
@current_user = current_user
@params = params
end
def execute
return error_no_license unless available?
return error_no_permissions unless allowed?
participant_params = Array(params[:participants])
return error_too_many_participants if participant_params.size > MAXIMUM_PARTICIPANTS
oncall_rotation = schedule.rotations.create(params.except(:participants))
return error_in_create(oncall_rotation) unless oncall_rotation.persisted?
new_participants = Array(participant_params).map do |participant|
OncallParticipant.new(
rotation: oncall_rotation,
user: participant[:user],
color_palette: participant[:color_palette],
color_weight: participant[:color_weight]
)
end
OncallParticipant.bulk_insert!(new_participants)
success(oncall_rotation)
end
private
attr_reader :schedule, :project, :current_user, :params, :participants
def allowed?
Ability.allowed?(current_user, :admin_incident_management_oncall_schedule, project)
end
def available?
::Gitlab::IncidentManagement.oncall_schedules_available?(project)
end
def error(message)
ServiceResponse.error(message: message)
end
def success(oncall_rotation)
ServiceResponse.success(payload: { oncall_rotation: oncall_rotation })
end
def error_too_many_participants
error("A maximum of #{MAXIMUM_PARTICIPANTS} participants can be added")
end
def error_no_permissions
error('You have insufficient permissions to create an on-call rotation for this project')
end
def error_no_license
error('Your license does not support on-call rotations')
end
def error_in_create(oncall_rotation)
error(oncall_rotation.errors.full_messages.to_sentence)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::OncallRotationPolicy do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:oncall_schedule) { create(:incident_management_oncall_schedule, project: project) }
let_it_be_with_refind(:oncall_rotation) { create(:incident_management_oncall_rotation, schedule: oncall_schedule) }
subject(:policy) { described_class.new(user, oncall_rotation) }
before do
stub_feature_flags(oncall_schedules_mvc: project)
stub_licensed_features(oncall_schedules: true)
end
describe 'rules' do
it { is_expected.to be_disallowed :read_incident_management_oncall_schedule }
context 'when reporter' do
before do
project.add_reporter(user)
end
it { is_expected.to be_allowed :read_incident_management_oncall_schedule }
context 'licensed feature disabled' do
before do
stub_licensed_features(oncall_schedules: false)
end
it { is_expected.to be_disallowed :read_incident_management_oncall_schedule }
end
end
end
end
...@@ -5,24 +5,32 @@ require 'spec_helper' ...@@ -5,24 +5,32 @@ require 'spec_helper'
RSpec.describe IncidentManagement::OncallSchedulePolicy do RSpec.describe IncidentManagement::OncallSchedulePolicy do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:oncall_schedule) { create(:incident_management_oncall_schedule, project: project) } let_it_be_with_refind(:oncall_schedule) { create(:incident_management_oncall_schedule, project: project) }
subject(:policy) { described_class.new(user, oncall_schedule) } subject(:policy) { described_class.new(user, oncall_schedule) }
before do before do
stub_licensed_features(oncall_schedules: true)
stub_feature_flags(oncall_schedules_mvc: project) stub_feature_flags(oncall_schedules_mvc: project)
stub_licensed_features(oncall_schedules: true)
end end
describe 'rules' do describe 'rules' do
it { is_expected.to be_disallowed :read_incident_management_oncall_schedule } it { is_expected.to be_disallowed :read_incident_management_oncall_schedule }
context 'when maintainer' do context 'when reporter' do
before do before do
project.add_maintainer(user) project.add_reporter(user)
end end
it { is_expected.to be_allowed :read_incident_management_oncall_schedule } it { is_expected.to be_allowed :read_incident_management_oncall_schedule }
context 'licensed feature disabled' do
before do
stub_licensed_features(oncall_schedules: false)
end
it { is_expected.to be_disallowed :read_incident_management_oncall_schedule }
end
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::OncallRotations::CreateService do
let_it_be_with_refind(:project) { create(:project) }
let_it_be(:schedule) { create(:incident_management_oncall_schedule, project: project) }
let_it_be(:user_with_permissions) { create(:user) }
let_it_be(:user_without_permissions) { create(:user) }
let_it_be(:current_user) { user_with_permissions }
let(:participants) do
[
{
user: current_user,
color_palette: 'blue',
color_weight: '500'
}
]
end
let(:params) { { name: 'On-call rotation', starts_at: Time.current, length: '1', length_unit: 'days' }.merge(participants: participants) }
let(:service) { described_class.new(schedule, project, current_user, params) }
before_all do
project.add_maintainer(user_with_permissions)
end
before do
stub_licensed_features(oncall_schedules: true)
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
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 create an on-call rotation for this project'
end
context 'when the current_user does not have permissions to create on-call schedules' do
let(:current_user) { user_without_permissions }
it_behaves_like 'error response', 'You have insufficient permissions to create an on-call rotation for this project'
end
context 'when feature is not available' do
before do
stub_licensed_features(oncall_schedules: false)
end
it_behaves_like 'error response', 'Your license does not support on-call rotations'
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(oncall_schedules_mvc: false)
end
it_behaves_like 'error response', 'Your license does not support on-call rotations'
end
context 'when an on-call rotation already exists' do
let!(:oncall_rotation) { create(:incident_management_oncall_rotation, schedule: schedule, name: 'On-call rotation') }
it_behaves_like 'error response', 'Name has already been taken'
end
context 'when too many participants' do
before do
stub_const('IncidentManagement::OncallRotations::CreateService::MAXIMUM_PARTICIPANTS', 0)
end
it 'has an informative error message' do
expect(execute).to be_error
expect(execute.message).to eq("A maximum of #{IncidentManagement::OncallRotations::CreateService::MAXIMUM_PARTICIPANTS} participants can be added")
end
end
context 'with valid params' do
it 'successfully creates an on-call rotation' do
expect(execute).to be_success
oncall_schedule = execute.payload[:oncall_rotation]
expect(oncall_schedule).to be_a(::IncidentManagement::OncallRotation)
expect(oncall_schedule.name).to eq('On-call rotation')
expect(oncall_schedule.length).to eq(1)
expect(oncall_schedule.length_unit).to eq('days')
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