diff --git a/CHANGELOG.md b/CHANGELOG.md index aee8ff4e..dc228a91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Discard old pending network requests in the UI (Users/Schedules) [#3172](https://github.com/grafana/oncall/pull/3172) - Fix resolution note source for mobile app by @vadimkerr ([#3174](https://github.com/grafana/oncall/pull/3174)) +- Fix iCal imported schedules related users and next shifts per user ([#3178](https://github.com/grafana/oncall/pull/3178)) - Fix references to removed access control functions in Grafana @mderynck ([#3184](https://github.com/grafana/oncall/pull/3184)) ## v1.3.45 (2023-10-19) diff --git a/engine/apps/api/tests/test_schedules.py b/engine/apps/api/tests/test_schedules.py index db37c36d..3385f0fd 100644 --- a/engine/apps/api/tests/test_schedules.py +++ b/engine/apps/api/tests/test_schedules.py @@ -1,4 +1,5 @@ import json +import textwrap from unittest.mock import patch import pytest @@ -1487,6 +1488,57 @@ def test_next_shifts_per_user( assert returned_data == expected +@pytest.mark.django_db +def test_next_shifts_per_user_ical_schedule_using_emails( + make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_auth_headers, make_schedule +): + organization, admin, token = make_organization_and_user_with_plugin_token() + client = APIClient() + + user = make_user_for_organization(organization, username="testing", email="testing@testing.com") + # ical file using emails as reference + cached_ical_primary_schedule = textwrap.dedent( + """ + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:testing + CALSCALE:GREGORIAN + BEGIN:VEVENT + CREATED:20220316T121102Z + LAST-MODIFIED:20230127T151619Z + DTSTAMP:20230127T151619Z + UID:something + SUMMARY:testing@testing.com + RRULE:FREQ=WEEKLY + DTSTART;TZID=Europe/Madrid:20220309T130000 + DTEND;TZID=Europe/Madrid:20220309T133000 + END:VEVENT + BEGIN:VEVENT + CREATED:20220316T121102Z + LAST-MODIFIED:20230127T151619Z + DTSTAMP:20230127T151619Z + UID:something-else + SUMMARY:testing_unknown@testing.com + RRULE:FREQ=WEEKLY + DTSTART;TZID=Europe/Madrid:20220309T150000 + DTEND;TZID=Europe/Madrid:20220309T153000 + END:VEVENT + END:VCALENDAR + """ + ) + schedule = make_schedule( + organization, + schedule_class=OnCallScheduleICal, + cached_ical_file_primary=cached_ical_primary_schedule, + ) + + url = reverse("api-internal:schedule-next-shifts-per-user", kwargs={"pk": schedule.public_primary_key}) + response = client.get(url, format="json", **make_user_auth_headers(admin, token)) + assert response.status_code == status.HTTP_200_OK + + assert set(response.data["users"].keys()) == {user.public_primary_key} + + @pytest.mark.django_db def test_related_users( make_organization_and_user_with_plugin_token, diff --git a/engine/apps/api/views/schedule.py b/engine/apps/api/views/schedule.py index 83121b1e..74908fe5 100644 --- a/engine/apps/api/views/schedule.py +++ b/engine/apps/api/views/schedule.py @@ -380,7 +380,7 @@ class ScheduleView( added_users = set() for e in events: user = e["users"][0]["pk"] if e["users"] else None - if user is not None and user not in added_users and e["end"] > now: + if user is not None and user not in added_users and user in users and e["end"] > now: users[user].update(e) added_users.add(user) diff --git a/engine/apps/schedules/models/on_call_schedule.py b/engine/apps/schedules/models/on_call_schedule.py index f96517df..26761bc5 100644 --- a/engine/apps/schedules/models/on_call_schedule.py +++ b/engine/apps/schedules/models/on_call_schedule.py @@ -1092,6 +1092,16 @@ class OnCallScheduleICal(OnCallSchedule): ) self.save(update_fields=["cached_ical_file_overrides", "prev_ical_file_overrides", "ical_file_error_overrides"]) + def related_users(self): + """Return users referenced in the schedule.""" + # combine users based on usernames and users via email (allowed in iCal based schedules) + usernames = [] + if self.cached_ical_file_primary: + usernames += RE_ICAL_FETCH_USERNAME.findall(self.cached_ical_file_primary) + if self.cached_ical_file_overrides: + usernames += RE_ICAL_FETCH_USERNAME.findall(self.cached_ical_file_overrides) + return self.organization.users.filter(Q(username__in=usernames) | Q(email__in=usernames)) + # Insight logs @property def insight_logs_serialized(self): diff --git a/engine/apps/schedules/tests/test_on_call_schedule.py b/engine/apps/schedules/tests/test_on_call_schedule.py index d9fd5cdb..16613e82 100644 --- a/engine/apps/schedules/tests/test_on_call_schedule.py +++ b/engine/apps/schedules/tests/test_on_call_schedule.py @@ -1304,6 +1304,40 @@ def test_schedule_related_users_usernames( assert set(schedule.related_users()) == set(users) +@pytest.mark.django_db +def test_schedule_related_users_emails(make_organization, make_user_for_organization, make_schedule): + organization = make_organization() + user = make_user_for_organization(organization, username="testing", email="testing@testing.com") + # ical file using email as reference + cached_ical_primary_schedule = textwrap.dedent( + """ + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:testing + CALSCALE:GREGORIAN + BEGIN:VEVENT + CREATED:20220316T121102Z + LAST-MODIFIED:20230127T151619Z + DTSTAMP:20230127T151619Z + UID:something + SUMMARY:testing@testing.com + RRULE:FREQ=WEEKLY;UNTIL=20221231T010101 + DTSTART;TZID=Europe/Madrid:20220309T130000 + DTEND;TZID=Europe/Madrid:20220309T133000 + SEQUENCE:4 + END:VEVENT + END:VCALENDAR + """ + ) + schedule = make_schedule( + organization, + schedule_class=OnCallScheduleICal, + cached_ical_file_primary=cached_ical_primary_schedule, + ) + + assert set(schedule.related_users()) == {user} + + @pytest.mark.django_db(transaction=True) def test_filter_events_none_cache_unchanged( make_organization, make_user_for_organization, make_schedule, make_on_call_shift