Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
b13eb238
Commit
b13eb238
authored
Jan 14, 2021
by
Sarah Yasonik
Committed by
Sean McGivern
Jan 14, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Generate shifts for a timeframe based on on-call rotation params
parent
0941a0b4
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
879 additions
and
3 deletions
+879
-3
ee/app/models/incident_management/oncall_participant.rb
ee/app/models/incident_management/oncall_participant.rb
+2
-0
ee/app/models/incident_management/oncall_rotation.rb
ee/app/models/incident_management/oncall_rotation.rb
+6
-1
ee/lib/incident_management/oncall_shift_generator.rb
ee/lib/incident_management/oncall_shift_generator.rb
+138
-0
ee/spec/lib/incident_management/oncall_shift_generator_spec.rb
...ec/lib/incident_management/oncall_shift_generator_spec.rb
+702
-0
ee/spec/models/incident_management/oncall_participant_spec.rb
...pec/models/incident_management/oncall_participant_spec.rb
+14
-2
ee/spec/models/incident_management/oncall_rotation_spec.rb
ee/spec/models/incident_management/oncall_rotation_spec.rb
+17
-0
No files found.
ee/app/models/incident_management/oncall_participant.rb
View file @
b13eb238
...
...
@@ -11,6 +11,8 @@ module IncidentManagement
belongs_to
:user
,
class_name:
'User'
,
foreign_key: :user_id
has_many
:shifts
,
class_name:
'OncallShift'
,
inverse_of: :participant
,
foreign_key: :participant_id
scope
:ordered_asc
,
->
{
order
(
id: :asc
)
}
# Uniqueness validations added here should be duplicated
# in IncidentManagement::OncallRotation::CreateService
# as bulk insertion skips validations
...
...
ee/app/models/incident_management/oncall_rotation.rb
View file @
b13eb238
...
...
@@ -19,9 +19,14 @@ module IncidentManagement
validates
:name
,
presence:
true
,
uniqueness:
{
scope: :oncall_schedule_id
},
length:
{
maximum:
NAME_LENGTH
}
validates
:starts_at
,
presence:
true
validates
:length
,
presence:
true
validates
:length
,
presence:
true
,
numericality:
true
validates
:length_unit
,
presence:
true
delegate
:project
,
to: :schedule
def
shift_duration
# As length_unit is an enum, input is guaranteed to be appropriate
length
.
public_send
(
length_unit
)
# rubocop:disable GitlabSecurity/PublicSend
end
end
end
ee/lib/incident_management/oncall_shift_generator.rb
0 → 100644
View file @
b13eb238
# frozen_string_literal: true
module
IncidentManagement
class
OncallShiftGenerator
# @param rotation [IncidentManagement::OncallRotation]
def
initialize
(
rotation
)
@rotation
=
rotation
end
# Generates an array of shifts which cover the provided time range.
#
# @param starts_at [ActiveSupport::TimeWithZone]
# @param ends_at [ActiveSupport::TimeWithZone]
# @return [IncidentManagement::OncallShift]
def
for_timeframe
(
starts_at
:,
ends_at
:)
starts_at
=
[
apply_timezone
(
starts_at
),
rotation_starts_at
].
max
ends_at
=
apply_timezone
(
ends_at
)
return
[]
unless
starts_at
<
ends_at
return
[]
unless
rotation
.
participants
.
any?
# The first shift within the timeframe may begin before
# the timeframe. We want to begin generating shifts
# based on the actual start time of the shift.
elapsed_shift_count
=
elapsed_whole_shifts
(
starts_at
)
shift_starts_at
=
shift_start_time
(
elapsed_shift_count
)
shifts
=
[]
while
shift_starts_at
<
ends_at
shifts
<<
shift_for
(
elapsed_shift_count
,
shift_starts_at
)
shift_starts_at
+=
shift_duration
elapsed_shift_count
+=
1
end
shifts
end
# Generates a single shift during which the timestamp occurs.
#
# @param timestamp [ActiveSupport::TimeWithZone]
# @return IncidentManagement::OncallShift
def
for_timestamp
(
timestamp
)
timestamp
=
apply_timezone
(
timestamp
)
return
if
timestamp
<
rotation_starts_at
return
unless
rotation
.
participants
.
any?
elapsed_shift_count
=
elapsed_whole_shifts
(
timestamp
)
shift_starts_at
=
shift_start_time
(
elapsed_shift_count
)
shift_for
(
elapsed_shift_count
,
shift_starts_at
)
end
private
attr_reader
:rotation
delegate
:shift_duration
,
to: :rotation
# Starting time of a shift which covers the timestamp.
# @return [ActiveSupport::TimeWithZone]
def
shift_start_time
(
elapsed_shift_count
)
rotation_starts_at
+
(
elapsed_shift_count
*
shift_duration
)
end
# Total completed shifts passed between rotation start
# time and the provided timestamp.
# @return [Integer]
def
elapsed_whole_shifts
(
timestamp
)
elapsed_duration
=
timestamp
-
rotation_starts_at
unless
rotation
.
hours?
# Changing timezones (like during daylight savings) can
# cause a "day" to have a duration other than 24 hours ("weeks" too).
# Since elapsed_duration is in seconds, we need
# account for this variable day/week length to
# determine how many actual shifts have elapsed.
#
# Ex) If a location with daylight savings sets their
# clocks forward an hour, a 1-day shift will last for
# 23 hours if it occurs over that transition.
#
# If we want to generate a shift which occurs 1 week
# after the timezone change, the real elapsed seconds
# will equal 1 week minus an hour.
#
# Seconds per average week: 2 * 7 * 24 * 60 * 60 = 1209600
# Seconds in zone-shifted week: 1209600 - (60 * 60) = 1206000
#
# If we count in seconds, minutes, or hours, these are different durations.
# If we count in "days" or "weeks", these durations are equivalent.
#
# To determine how many effective days or weeks
# a duration (in seconds) was, we need to normalize
# the duration to fit the definition of a 24-hour day.
# We can do this by diffing the UTC-offsets between the
# start time of the rotation and the relevant timestamp.
# This should account for different hemispheres,
# offsets changes other an 1 hour, and one-off timezone changes.
elapsed_duration
+=
timestamp
.
utc_offset
-
rotation_starts_at
.
utc_offset
end
# Uses #round to account for floating point inconsistencies.
(
elapsed_duration
/
shift_duration
).
round
(
5
).
floor
end
# Returns an UNSAVED shift, as this shift won't necessarily
# be persisted.
# @return [IncidentManagement::OncallShift]
def
shift_for
(
elapsed_shift_count
,
shift_starts_at
)
IncidentManagement
::
OncallShift
.
new
(
rotation:
rotation
,
participant:
participants
[
participant_rank
(
elapsed_shift_count
)],
starts_at:
shift_starts_at
,
ends_at:
shift_starts_at
+
shift_duration
)
end
# Position in an array of participants based on the
# number of shifts which have elasped for the rotation.
# @return [Integer]
def
participant_rank
(
elapsed_shifts_count
)
elapsed_shifts_count
%
participants
.
length
end
def
participants
@participants
||=
rotation
.
participants
.
ordered_asc
end
def
rotation_starts_at
@rotation_starts_at
||=
apply_timezone
(
rotation
.
starts_at
)
end
def
apply_timezone
(
timestamp
)
timestamp
.
in_time_zone
(
rotation
.
schedule
.
timezone
)
end
end
end
ee/spec/lib/incident_management/oncall_shift_generator_spec.rb
0 → 100644
View file @
b13eb238
This diff is collapsed.
Click to expand it.
ee/spec/models/incident_management/oncall_participant_spec.rb
View file @
b13eb238
...
...
@@ -14,13 +14,13 @@ RSpec.describe IncidentManagement::OncallParticipant do
rotation
.
project
.
add_developer
(
user
)
end
describe
'
.
associations'
do
describe
'associations'
do
it
{
is_expected
.
to
belong_to
(
:rotation
)
}
it
{
is_expected
.
to
belong_to
(
:user
)
}
it
{
is_expected
.
to
have_many
(
:shifts
)
}
end
describe
'
.
validations'
do
describe
'validations'
do
it
{
is_expected
.
to
validate_presence_of
(
:rotation
)
}
it
{
is_expected
.
to
validate_presence_of
(
:user
)
}
it
{
is_expected
.
to
validate_presence_of
(
:color_weight
)
}
...
...
@@ -38,6 +38,18 @@ RSpec.describe IncidentManagement::OncallParticipant do
end
end
describe
'scopes'
do
describe
'.ordered_asc'
do
let_it_be
(
:participant1
)
{
create
(
:incident_management_oncall_participant
,
:with_developer_access
,
rotation:
rotation
)
}
let_it_be
(
:participant2
)
{
create
(
:incident_management_oncall_participant
,
:with_developer_access
,
rotation:
rotation
)
}
let_it_be
(
:participant3
)
{
create
(
:incident_management_oncall_participant
,
:with_developer_access
,
rotation:
rotation
)
}
subject
{
described_class
.
ordered_asc
}
it
{
is_expected
.
to
eq
([
participant1
,
participant2
,
participant3
])
}
end
end
private
def
remove_user_from_project
(
user
,
project
)
...
...
ee/spec/models/incident_management/oncall_rotation_spec.rb
View file @
b13eb238
...
...
@@ -20,6 +20,7 @@ RSpec.describe IncidentManagement::OncallRotation do
it
{
is_expected
.
to
validate_uniqueness_of
(
:name
).
scoped_to
(
:oncall_schedule_id
)
}
it
{
is_expected
.
to
validate_presence_of
(
:starts_at
)
}
it
{
is_expected
.
to
validate_presence_of
(
:length
)
}
it
{
is_expected
.
to
validate_numericality_of
(
:length
)
}
it
{
is_expected
.
to
validate_presence_of
(
:length_unit
)
}
context
'when the oncall rotation with the same name exists'
do
...
...
@@ -33,4 +34,20 @@ RSpec.describe IncidentManagement::OncallRotation do
end
end
end
describe
'#shift_duration'
do
let_it_be
(
:rotation
)
{
create
(
:incident_management_oncall_rotation
,
schedule:
schedule
,
length:
5
,
length_unit: :days
)
}
subject
{
rotation
.
shift_duration
}
it
{
is_expected
.
to
eq
(
5
.
days
)
}
described_class
.
length_units
.
each_key
do
|
unit
|
context
"with a length unit of
#{
unit
}
"
do
let
(
:rotation
)
{
build
(
:incident_management_oncall_rotation
,
schedule:
schedule
,
length_unit:
unit
)
}
it
{
is_expected
.
to
be_a
(
ActiveSupport
::
Duration
)
}
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment