From 5a8d2baa284ce99b21a1801edb4c21d2fa2ff0f5 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Tue, 16 Apr 2024 13:56:41 -0400 Subject: [PATCH] ignore shift swap generation for gcal events with specific keyword in title (#4228) # What this PR does Related to [this](https://raintank-corp.slack.com/archives/C0229FD3CE9/p1713193452179019) internal feature request ## 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 --- .../on-call-schedules/shift-swaps/index.md | 5 +++ engine/apps/google/constants.py | 2 + engine/apps/google/tasks.py | 13 ++++++- ..._out_of_office_calendar_events_for_user.py | 38 ++++++++++++++----- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/docs/sources/manage/on-call-schedules/shift-swaps/index.md b/docs/sources/manage/on-call-schedules/shift-swaps/index.md index 9b8d26d4..f4c3a4e9 100644 --- a/docs/sources/manage/on-call-schedules/shift-swaps/index.md +++ b/docs/sources/manage/on-call-schedules/shift-swaps/index.md @@ -109,6 +109,11 @@ under the Google Calendar tab. Once linked, you can further configure things, su like us to consider for automatic shift swap generation (by default we will consider all of the schedules that you are involved in). +### Ignoring events + +If you would like to have Grafana OnCall ignore a specific Out of Office event from being considered for +Shift Swap Request generation, simply add `#grafana-oncall-ignore` to the Out of Office event's title. + ### Configuring for open source 1. Follow the instructions [here](https://developers.google.com/identity/protocols/oauth2) to setup your Google OAuth2 diff --git a/engine/apps/google/constants.py b/engine/apps/google/constants.py index 8d91becf..f0f58bae 100644 --- a/engine/apps/google/constants.py +++ b/engine/apps/google/constants.py @@ -1,3 +1,5 @@ +EVENT_SUMMARY_IGNORE_KEYWORD = "#grafana-oncall-ignore" + GOOGLE_CALENDAR_EVENT_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z" """ https://stackoverflow.com/a/17159470/3902555 diff --git a/engine/apps/google/tasks.py b/engine/apps/google/tasks.py index bfd3dea1..dd7ab9a9 100644 --- a/engine/apps/google/tasks.py +++ b/engine/apps/google/tasks.py @@ -2,6 +2,7 @@ import logging from celery.utils.log import get_task_logger +from apps.google import constants from apps.google.client import GoogleCalendarAPIClient from apps.google.models import GoogleOAuth2User from apps.schedules.models import OnCallSchedule, ShiftSwapRequest @@ -31,7 +32,10 @@ def sync_out_of_office_calendar_events_for_user(google_oauth2_user_pk: int) -> N users_schedules = users_schedules.filter(public_primary_key__in=oncall_schedules_to_consider_for_shift_swaps) for out_of_office_event in google_api_client.fetch_out_of_office_events(): - event_id = out_of_office_event.raw_event["id"] + raw_event = out_of_office_event.raw_event + + event_title = raw_event["summary"] + event_id = raw_event["id"] start_time_utc = out_of_office_event.start_time_utc end_time_utc = out_of_office_event.end_time_utc @@ -40,6 +44,13 @@ def sync_out_of_office_calendar_events_for_user(google_oauth2_user_pk: int) -> N f"{end_time_utc} for user {user_id}" ) + if constants.EVENT_SUMMARY_IGNORE_KEYWORD in event_title.lower(): + logger.info( + f"Skipping out of office event {event_id} because it contains the ignore keyword " + f"'{constants.EVENT_SUMMARY_IGNORE_KEYWORD}'" + ) + continue + for schedule in users_schedules: _, current_shifts, upcoming_shifts = schedule.shifts_for_user( user, diff --git a/engine/apps/google/tests/test_sync_out_of_office_calendar_events_for_user.py b/engine/apps/google/tests/test_sync_out_of_office_calendar_events_for_user.py index 1d3640e1..0a58f03a 100644 --- a/engine/apps/google/tests/test_sync_out_of_office_calendar_events_for_user.py +++ b/engine/apps/google/tests/test_sync_out_of_office_calendar_events_for_user.py @@ -8,7 +8,9 @@ from apps.google import constants, tasks from apps.schedules.models import CustomOnCallShift, OnCallScheduleWeb, ShiftSwapRequest -def _create_mock_google_calendar_event(start_time: datetime.datetime, end_time: datetime.datetime): +def _create_mock_google_calendar_event( + start_time: datetime.datetime, end_time: datetime.datetime, summary="Out of office" +): return { "colorId": "4", "created": "2024-03-22T23:06:39.000Z", @@ -50,7 +52,7 @@ def _create_mock_google_calendar_event(start_time: datetime.datetime, end_time: "timeZone": "America/New_York", }, "status": "confirmed", - "summary": "Out of office", + "summary": summary, "updated": "2024-03-22T23:06:44.299Z", "visibility": "public", } @@ -156,11 +158,25 @@ def test_sync_out_of_office_calendar_events_for_user_no_ooo_events(mock_google_a @patch("apps.google.client.build") +@pytest.mark.parametrize( + "out_of_office_event_title,should_create_ssr", + [ + ("Out of office", True), + ("Out of office grafana-oncall-ignore", True), + ("Out of office #grafana-oncall-ignore", False), + ("Out of office #GRAFANA-ONCALL-IGNORE", False), + ], +) @pytest.mark.django_db -def test_sync_out_of_office_calendar_events_for_user_single_ooo_event(mock_google_api_client_build, test_setup): +def test_sync_out_of_office_calendar_events_for_user_single_ooo_event( + mock_google_api_client_build, + test_setup, + out_of_office_event_title, + should_create_ssr, +): start_time, end_time = _create_event_start_and_end_times() out_of_office_events = [ - _create_mock_google_calendar_event(start_time, end_time), + _create_mock_google_calendar_event(start_time, end_time, out_of_office_event_title), ] mock_google_api_client_build.return_value.events.return_value.list.return_value.execute.return_value = { @@ -173,13 +189,17 @@ def test_sync_out_of_office_calendar_events_for_user_single_ooo_event(mock_googl tasks.sync_out_of_office_calendar_events_for_user(google_oauth2_user.pk) ssrs = ShiftSwapRequest.objects.filter(beneficiary=user, schedule=schedule) - ssr = ssrs.first() - assert ssrs.count() == 1 + if should_create_ssr: + assert ssrs.count() == 1 - assert ssr.swap_start == start_time - assert ssr.swap_end == end_time - assert ssr.description == f"{user.name} will be out of office during this time according to Google Calendar" + ssr = ssrs.first() + + assert ssr.swap_start == start_time + assert ssr.swap_end == end_time + assert ssr.description == f"{user.name} will be out of office during this time according to Google Calendar" + else: + assert ssrs.count() == 0 @patch("apps.google.client.build")