Commit 00e27eca authored by Dmitry Gruzd's avatar Dmitry Gruzd

Merge branch '322079-allow-overnight-oncall-shifts' into 'master'

Allow overnight restricted shift times

See merge request gitlab-org/gitlab!57202
parents 3c3d6079 8289f62f
...@@ -92,18 +92,6 @@ module Mutations ...@@ -92,18 +92,6 @@ module Mutations
raise invalid_time_error unless TIME_FORMAT.match?(start_time) raise invalid_time_error unless TIME_FORMAT.match?(start_time)
raise invalid_time_error unless TIME_FORMAT.match?(end_time) raise invalid_time_error unless TIME_FORMAT.match?(end_time)
# We parse the times into dates to compare.
# Time.parse parses a timestamp into a Time with todays date
# Time.parse("22:11") => 2021-02-23 22:11:00 +0000
parsed_from = Time.parse(start_time)
parsed_to = Time.parse(end_time)
# Overnight shift times will be supported via
# https://gitlab.com/gitlab-org/gitlab/-/issues/322079
if parsed_to < parsed_from
raise ::Gitlab::Graphql::Errors::ArgumentError, "'start_time' time must be before 'end_time' time"
end
[start_time, end_time] [start_time, end_time]
end end
......
...@@ -11,10 +11,12 @@ module IncidentManagement ...@@ -11,10 +11,12 @@ module IncidentManagement
end_time > start_time if present? end_time > start_time if present?
end end
def for_date(date) def for_date(start_date)
end_date = end_after_start? ? start_date : start_date.next_day
[ [
date.change(hour: start_time.hour, min: start_time.min), start_date.change(hour: start_time.hour, min: start_time.min),
date.change(hour: end_time.hour, min: end_time.min) end_date.change(hour: end_time.hour, min: end_time.min)
] ]
end end
end end
...@@ -44,7 +46,6 @@ module IncidentManagement ...@@ -44,7 +46,6 @@ module IncidentManagement
validates :active_period_start, presence: true, if: :active_period_end validates :active_period_start, presence: true, if: :active_period_end
validates :active_period_end, presence: true, if: :active_period_start validates :active_period_end, presence: true, if: :active_period_start
validate :active_period_end_after_start, if: :active_period_start
validate :no_active_period_for_hourly_shifts, if: :hours? validate :no_active_period_for_hourly_shifts, if: :hours?
scope :in_progress, -> { where('starts_at < :time AND (ends_at > :time OR ends_at IS NULL)', time: Time.current) } scope :in_progress, -> { where('starts_at < :time AND (ends_at > :time OR ends_at IS NULL)', time: Time.current) }
...@@ -98,13 +99,6 @@ module IncidentManagement ...@@ -98,13 +99,6 @@ module IncidentManagement
errors.add(:ends_at, s_('must be after start')) if ends_at <= starts_at errors.add(:ends_at, s_('must be after start')) if ends_at <= starts_at
end end
def active_period_end_after_start
return unless active_period.present?
return if active_period.end_after_start?
errors.add(:active_period_end, _('must be later than active period start'))
end
def no_active_period_for_hourly_shifts def no_active_period_for_hourly_shifts
if active_period_start || active_period_end if active_period_start || active_period_end
errors.add(:length_unit, _('Restricted shift times are not available for hourly shifts')) errors.add(:length_unit, _('Restricted shift times are not available for hourly shifts'))
......
---
title: Allow overnight shifts for oncall rotations
merge_request: 57202
author:
type: added
...@@ -129,8 +129,8 @@ module IncidentManagement ...@@ -129,8 +129,8 @@ module IncidentManagement
# expected_shift_count = 14 -> pretend it's a 2-week rotation # expected_shift_count = 14 -> pretend it's a 2-week rotation
# shift_count = 2 -> we're calculating the shift for the 3rd day # shift_count = 2 -> we're calculating the shift for the 3rd day
# starts_at = Monday 00:00:00 + 8.hours + 2.days => Thursday 08:00:00 # starts_at = Monday 00:00:00 + 8.hours + 2.days => Thursday 08:00:00
start_date = shift_cycle_starts_at + shift_count.days
starts_at, ends_at = rotation.active_period.for_date(shift_cycle_starts_at + shift_count.days) starts_at, ends_at = rotation.active_period.for_date(start_date)
shift_for(participant, [rotation.starts_at, starts_at].max, limit_end_time(ends_at)) shift_for(participant, [rotation.starts_at, starts_at].max, limit_end_time(ends_at))
end end
......
...@@ -124,8 +124,11 @@ RSpec.describe Mutations::IncidentManagement::OncallRotation::Create do ...@@ -124,8 +124,11 @@ RSpec.describe Mutations::IncidentManagement::OncallRotation::Create do
let(:start_time) { '17:00' } let(:start_time) { '17:00' }
let(:end_time) { '08:00' } let(:end_time) { '08:00' }
it 'raises an error' do it 'saves the on-call rotation with active period times' do
expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "'start_time' time must be before 'end_time' time") rotation = resolve[:oncall_rotation]
expect(rotation.active_period_start.strftime('%H:%M')).to eql('17:00')
expect(rotation.active_period_end.strftime('%H:%M')).to eql('08:00')
end end
end end
......
...@@ -155,8 +155,11 @@ RSpec.describe Mutations::IncidentManagement::OncallRotation::Update do ...@@ -155,8 +155,11 @@ RSpec.describe Mutations::IncidentManagement::OncallRotation::Update do
let(:start_time) { '17:00' } let(:start_time) { '17:00' }
let(:end_time) { '08:00' } let(:end_time) { '08:00' }
it 'raises an error' do it 'saves the on-call rotation with active period times' do
expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "'start_time' time must be before 'end_time' time") rotation = resolve[:oncall_rotation]
expect(rotation.active_period_start.strftime('%H:%M')).to eql('17:00')
expect(rotation.active_period_end.strftime('%H:%M')).to eql('08:00')
end end
end end
......
...@@ -93,10 +93,13 @@ RSpec.describe IncidentManagement::OncallShiftGenerator do ...@@ -93,10 +93,13 @@ RSpec.describe IncidentManagement::OncallShiftGenerator do
[:participant3, '2020-12-18 00:00:00 UTC', '2020-12-23 00:00:00 UTC']] [:participant3, '2020-12-18 00:00:00 UTC', '2020-12-23 00:00:00 UTC']]
context 'with shift active period times set' do context 'with shift active period times set' do
let(:active_period_start) { "08:00" }
let(:active_period_end) { "17:00" }
before do before do
rotation.update!( rotation.update!(
active_period_start: "08:00", active_period_start: active_period_start,
active_period_end: "17:00" active_period_end: active_period_end
) )
end end
...@@ -165,6 +168,28 @@ RSpec.describe IncidentManagement::OncallShiftGenerator do ...@@ -165,6 +168,28 @@ RSpec.describe IncidentManagement::OncallShiftGenerator do
[:participant2, '2020-12-16 08:00:00 UTC', '2020-12-16 17:00:00 UTC'], [:participant2, '2020-12-16 08:00:00 UTC', '2020-12-16 17:00:00 UTC'],
[:participant2, '2020-12-17 08:00:00 UTC', '2020-12-17 17:00:00 UTC']] [:participant2, '2020-12-17 08:00:00 UTC', '2020-12-17 17:00:00 UTC']]
end end
context 'active period is overnight' do
let(:active_period_start) { "17:00" }
let(:active_period_end) { "08:00" }
it 'splits the shifts daily by each active period' do
expect(shifts.count).to eq (ends_at.to_date - starts_at.to_date).to_i
end
it_behaves_like 'unsaved shifts',
'5 shifts for each participant with overnight shifts',
[[:participant1, '2020-12-08 17:00:00 UTC', '2020-12-09 08:00:00 UTC'],
[:participant1, '2020-12-09 17:00:00 UTC', '2020-12-10 08:00:00 UTC'],
[:participant1, '2020-12-10 17:00:00 UTC', '2020-12-11 08:00:00 UTC'],
[:participant1, '2020-12-11 17:00:00 UTC', '2020-12-12 08:00:00 UTC'],
[:participant1, '2020-12-12 17:00:00 UTC', '2020-12-13 08:00:00 UTC'],
[:participant2, '2020-12-13 17:00:00 UTC', '2020-12-14 08:00:00 UTC'],
[:participant2, '2020-12-14 17:00:00 UTC', '2020-12-15 08:00:00 UTC'],
[:participant2, '2020-12-15 17:00:00 UTC', '2020-12-16 08:00:00 UTC'],
[:participant2, '2020-12-16 17:00:00 UTC', '2020-12-17 08:00:00 UTC'],
[:participant2, '2020-12-17 17:00:00 UTC', '2020-12-18 08:00:00 UTC']]
end
end end
context 'when end time is earlier than start time' do context 'when end time is earlier than start time' do
......
...@@ -93,16 +93,6 @@ RSpec.describe IncidentManagement::OncallRotation do ...@@ -93,16 +93,6 @@ RSpec.describe IncidentManagement::OncallRotation do
expect(subject.errors.full_messages).to include(/Restricted shift times are not available for hourly shifts/) expect(subject.errors.full_messages).to include(/Restricted shift times are not available for hourly shifts/)
end end
end end
context 'end time before start time' do
it 'raises a validation error if an active period is set' do
subject.active_period_start = '17:00'
subject.active_period_end = '08:00'
expect(subject.valid?).to eq(false)
expect(subject.errors.full_messages).to include('Active period end must be later than active period start')
end
end
end end
end end
......
...@@ -163,6 +163,14 @@ RSpec.describe IncidentManagement::OncallRotations::CreateService do ...@@ -163,6 +163,14 @@ RSpec.describe IncidentManagement::OncallRotations::CreateService do
it_behaves_like 'successfully creates rotation' it_behaves_like 'successfully creates rotation'
it_behaves_like 'saved the active period times' it_behaves_like 'saved the active period times'
context 'when end active time is before start active time' do
let(:active_period_start) { '17:00' }
let(:active_period_end) { '08:00' }
it_behaves_like 'successfully creates rotation'
it_behaves_like 'saved the active period times'
end
context 'when only active period end time is set' do context 'when only active period end time is set' do
let(:active_period_start) { nil } let(:active_period_start) { nil }
...@@ -174,13 +182,6 @@ RSpec.describe IncidentManagement::OncallRotations::CreateService do ...@@ -174,13 +182,6 @@ RSpec.describe IncidentManagement::OncallRotations::CreateService do
it_behaves_like 'error response', "Active period end can't be blank" it_behaves_like 'error response', "Active period end can't be blank"
end end
context 'when end active time is before start active time' do
let(:active_period_start) { '17:00' }
let(:active_period_end) { '08:00' }
it_behaves_like 'error response', "Active period end must be later than active period start"
end
end end
context 'for an in-progress rotation' do context 'for an in-progress rotation' do
......
...@@ -36543,9 +36543,6 @@ msgstr "" ...@@ -36543,9 +36543,6 @@ msgstr ""
msgid "must be greater than start date" msgid "must be greater than start date"
msgstr "" msgstr ""
msgid "must be later than active period start"
msgstr ""
msgid "must contain only valid frameworks" msgid "must contain only valid frameworks"
msgstr "" 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