From 1ba742d99e3c33766ece5a64ef2776f73d1fc8a6 Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Tue, 16 Aug 2022 18:13:31 -0300 Subject: [PATCH] Fix for final event calculation when splitting events --- .../apps/schedules/models/on_call_schedule.py | 21 ++++-- .../schedules/tests/test_on_call_schedule.py | 67 +++++++++++++++++++ 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/engine/apps/schedules/models/on_call_schedule.py b/engine/apps/schedules/models/on_call_schedule.py index 98d605f3..d05cc0f4 100644 --- a/engine/apps/schedules/models/on_call_schedule.py +++ b/engine/apps/schedules/models/on_call_schedule.py @@ -276,14 +276,18 @@ class OnCallSchedule(PolymorphicModel): if not events: return [] - # sort schedule events by (type desc, priority desc, start timestamp asc) - events.sort( - key=lambda e: ( - -e["calendar_type"] if e["calendar_type"] else 0, # overrides: 1, shifts: 0, gaps: None - -e["priority_level"] if e["priority_level"] else 0, - e["start"], + def apply_sorting(eventlist): + """Sort events keeping the events priority criteria.""" + eventlist.sort( + key=lambda e: ( + -e["calendar_type"] if e["calendar_type"] else 0, # overrides: 1, shifts: 0, gaps: None + -e["priority_level"] if e["priority_level"] else 0, + e["start"], + ) ) - ) + + # sort schedule events by (type desc, priority desc, start timestamp asc) + apply_sorting(events) def _merge_intervals(evs): """Keep track of scheduled intervals.""" @@ -345,6 +349,9 @@ class OnCallSchedule(PolymorphicModel): # event ends after current interval, update event start timestamp to match the interval end # and process the updated event as any other event ev["start"] = intervals[current_interval_idx][1] + # reorder pending events after updating current event start date + # (ie. insert the event where it should be to keep the order criteria) + apply_sorting(pending) else: # done, go to next event current_event_idx += 1 diff --git a/engine/apps/schedules/tests/test_on_call_schedule.py b/engine/apps/schedules/tests/test_on_call_schedule.py index a6e875ba..9fae6517 100644 --- a/engine/apps/schedules/tests/test_on_call_schedule.py +++ b/engine/apps/schedules/tests/test_on_call_schedule.py @@ -322,6 +322,73 @@ def test_final_schedule_events(make_organization, make_user_for_organization, ma assert returned_events == expected_events +@pytest.mark.django_db +def test_final_schedule_splitting_events( + make_organization, make_user_for_organization, make_on_call_shift, make_schedule +): + organization = make_organization() + schedule = make_schedule( + organization, + schedule_class=OnCallScheduleWeb, + name="test_web_schedule", + ) + + now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + start_date = now - timezone.timedelta(days=7) + + user_a, user_b, user_c = (make_user_for_organization(organization, username=i) for i in "ABC") + + shifts = ( + # user, priority, start time (h), duration (hs) + (user_a, 1, 10, 10), # r1-1: 10-20 / A + (user_b, 1, 12, 4), # r1-2: 12-16 / B + (user_c, 2, 15, 3), # r2-1: 15-18 / C + ) + for user, priority, start_h, duration in shifts: + data = { + "start": start_date + timezone.timedelta(hours=start_h), + "rotation_start": start_date + timezone.timedelta(hours=start_h), + "duration": timezone.timedelta(hours=duration), + "priority_level": priority, + "frequency": CustomOnCallShift.FREQUENCY_DAILY, + "schedule": schedule, + } + on_call_shift = make_on_call_shift( + organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data + ) + on_call_shift.add_rolling_users([[user]]) + + returned_events = schedule.final_events("UTC", start_date, days=1) + + expected = ( + # start (h), duration (H), user, priority + (10, 5, "A", 1), # 10-15 A + (12, 3, "B", 1), # 12-15 B + (15, 3, "C", 2), # 15-18 C + (18, 2, "A", 1), # 18-20 A + ) + expected_events = [ + { + "end": start_date + timezone.timedelta(hours=start + duration), + "priority_level": priority, + "start": start_date + timezone.timedelta(hours=start), + "user": user, + } + for start, duration, user, priority in expected + ] + returned_events = [ + { + "end": e["end"], + "priority_level": e["priority_level"], + "start": e["start"], + "user": e["users"][0]["display_name"] if e["users"] else None, + } + for e in returned_events + if not e["is_gap"] + ] + assert returned_events == expected_events + + @pytest.mark.django_db def test_preview_shift(make_organization, make_user_for_organization, make_schedule, make_on_call_shift): organization = make_organization()