Skip to content

Commit

Permalink
Fix for user appearing in shift rotation when they should not be (#5064)
Browse files Browse the repository at this point in the history
# What this PR does
Fix calculation for interval when building ical events for schedules
with end dates and masked days. Add a test which makes it easier to test
different cases that can occur.

## Which issue(s) this PR closes

Related to grafana/support-escalations#12388

<!--
*Note*: If you want the issue to be auto-closed once the PR is merged,
change "Related to" to "Closes" in the line above.
If you have more than one GitHub issue that this PR closes, be sure to
preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.

---------

Co-authored-by: Matias Bordese <mbordese@gmail.com>
  • Loading branch information
mderynck and matiasb authored Oct 3, 2024
1 parent ee2ae50 commit c65a3c9
Show file tree
Hide file tree
Showing 3 changed files with 581 additions and 18 deletions.
51 changes: 33 additions & 18 deletions engine/apps/schedules/models/custom_on_call_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,38 +323,53 @@ def _daily_by_day_to_ical(self, time_zone, start, users_queue):
# we may need to iterate several times over users until we get a seen combination
# use the group index as reference since user groups could repeat in the queue
cycle_user_groups = itertools.cycle(range(len(users_queue)))
orig_start = last_start = start
previous_day = None
all_rotations_checked = False
# we need to go through each individual day
day_by_day_rrule = copy.deepcopy(self.event_ical_rules)
day_by_day_rrule["interval"] = 1
for user_group_id in cycle_user_groups:
for i in range(self.interval):
if not start: # means that rotation ends before next event starts
all_rotations_checked = True
break
last_start = start
day = CustomOnCallShift.ICAL_WEEKDAY_MAP[start.weekday()]
# double-check day is valid (when until is set, we may get unexpected days)
if day in self.by_day:
if not start:
# means that rotation ended before next event starts
# keep iterating to track missing combinations and get the right week_interval
if previous_day is None:
day = self.by_day[0]
else:
previous_index = self.by_day.index(previous_day)
day = self.by_day[(previous_index + 1) % len(self.by_day)]

if (user_group_id, day, i) in combinations:
all_rotations_checked = True
break

starting_dates.append(start)
combinations.append((user_group_id, day, i))
# get next event date following the original rule
event_ical = self.generate_ical(start, 1, None, 1, time_zone, custom_rrule=day_by_day_rrule)
start = self.get_rotation_date(event_ical, get_next_date=True, interval=1)
previous_day = day
else:
day = CustomOnCallShift.ICAL_WEEKDAY_MAP[start.weekday()]
# double-check day is valid (when until is set, we may get unexpected days)
if day in self.by_day:
if (user_group_id, day, i) in combinations:
all_rotations_checked = True
break

starting_dates.append(start)
combinations.append((user_group_id, day, i))
previous_day = day
# get next event date following the original rule
event_ical = self.generate_ical(start, 1, None, 1, time_zone, custom_rrule=day_by_day_rrule)
start = self.get_rotation_date(event_ical, get_next_date=True, interval=1)

if all_rotations_checked:
break

week_interval = 1
if orig_start and last_start:
# number of weeks used to cover all combinations
week_interval = ((last_start - orig_start).days // 7) or 1
# interval is given by the number of weeks required to cover all combinations
week_interval = (len(combinations) // len(self.by_day)) or 1

counter = 1
for (user_group_id, day, _), start in zip(combinations, starting_dates):
for (user_group_id, day, _), start in itertools.zip_longest(combinations, starting_dates, fillvalue=None):
if not start:
# means that rotation ended before next event starts, no more events to generate
break
users = users_queue[user_group_id]
for user_counter, user in enumerate(users, start=1):
# setup weekly events, for each user group/day combinations,
Expand Down
Loading

0 comments on commit c65a3c9

Please sign in to comment.