diff --git a/CHANGELOG.md b/CHANGELOG.md index 046d0810..dbb154e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Data type changed from `DateField` to `DateTimeField` on the `final_shifts` API endpoint. Endpoint now accepts either +a date or a datetime ([#3103](https://github.com/grafana/oncall/pull/3103)) + ### Changed - Simplify Direct Paging workflow. Now when using Direct Paging you either simply specify a team, or one or more users diff --git a/engine/apps/public_api/serializers/schedules_base.py b/engine/apps/public_api/serializers/schedules_base.py index 106df5c2..d0b5b192 100644 --- a/engine/apps/public_api/serializers/schedules_base.py +++ b/engine/apps/public_api/serializers/schedules_base.py @@ -75,8 +75,8 @@ class ScheduleBaseSerializer(serializers.ModelSerializer): class FinalShiftQueryParamsSerializer(serializers.Serializer): - start_date = serializers.DateField(required=True) - end_date = serializers.DateField(required=True) + start_date = serializers.DateTimeField(required=True, input_formats=["%Y-%m-%dT%H:%M", "%Y-%m-%d"]) + end_date = serializers.DateTimeField(required=True, input_formats=["%Y-%m-%dT%H:%M", "%Y-%m-%d"]) def validate(self, attrs): if attrs["start_date"] > attrs["end_date"]: diff --git a/engine/apps/public_api/tests/test_schedules.py b/engine/apps/public_api/tests/test_schedules.py index 45a78dd1..2135d2f9 100644 --- a/engine/apps/public_api/tests/test_schedules.py +++ b/engine/apps/public_api/tests/test_schedules.py @@ -876,7 +876,7 @@ def test_oncall_shifts_request_validation( organization, _, token = make_organization_and_user_with_token() web_schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb) - valid_date_msg = "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + valid_date_msg = "Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm, YYYY-MM-DD." client = APIClient() @@ -917,6 +917,23 @@ def test_oncall_shifts_request_validation( ] } + # datetime validation + # invalid request (doesnt match pattern YYYY-MM-DDThh:mm) + response = _make_request(web_schedule, "?start_date=2021-01-01 01:00") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert ( + response.json()["start_date"][0] + == "Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm, YYYY-MM-DD." + ) + + # valid request both parameters using datetime + response = _make_request(web_schedule, "?start_date=2021-01-01T01:00&end_date=2021-01-02T01:00") + assert response.status_code == status.HTTP_200_OK + + # valid request combination of date and datetime + response = _make_request(web_schedule, "?start_date=2021-01-01&end_date=2021-01-02T01:00") + assert response.status_code == status.HTTP_200_OK + @pytest.mark.django_db def test_oncall_shifts_export( @@ -958,7 +975,9 @@ def test_oncall_shifts_export( client = APIClient() url = reverse("api-public:schedules-final-shifts", kwargs={"pk": schedule.public_primary_key}) - response = client.get(f"{url}?start_date=2023-01-01&end_date=2023-02-01", format="json", HTTP_AUTHORIZATION=token) + response = client.get( + f"{url}?start_date=2023-01-01T18:00&end_date=2023-02-01", format="json", HTTP_AUTHORIZATION=token + ) assert response.status_code == status.HTTP_200_OK expected_on_call_times = { @@ -1018,7 +1037,9 @@ def test_oncall_shifts_export_from_ical_schedule( client = APIClient() url = reverse("api-public:schedules-final-shifts", kwargs={"pk": schedule.public_primary_key}) - response = client.get(f"{url}?start_date=2023-07-01&end_date=2023-07-31", format="json", HTTP_AUTHORIZATION=token) + response = client.get( + f"{url}?start_date=2023-07-01T09:00&end_date=2023-07-31T21:00", format="json", HTTP_AUTHORIZATION=token + ) assert response.status_code == status.HTTP_200_OK expected_on_call_times = { @@ -1055,7 +1076,9 @@ def test_oncall_shifts_export_from_api_schedule( client = APIClient() url = reverse("api-public:schedules-final-shifts", kwargs={"pk": schedule.public_primary_key}) - response = client.get(f"{url}?start_date=2023-07-01&end_date=2023-07-31", format="json", HTTP_AUTHORIZATION=token) + response = client.get( + f"{url}?start_date=2023-07-01T09:00&end_date=2023-07-31T11:00", format="json", HTTP_AUTHORIZATION=token + ) assert response.status_code == status.HTTP_200_OK expected_on_call_times = { @@ -1098,7 +1121,9 @@ def test_oncall_shifts_export_truncate_events( # request shifts on a Tu (ie. 00:00 - 09:00) url = reverse("api-public:schedules-final-shifts", kwargs={"pk": schedule.public_primary_key}) - response = client.get(f"{url}?start_date=2023-01-03&end_date=2023-01-03", format="json", HTTP_AUTHORIZATION=token) + response = client.get( + f"{url}?start_date=2023-01-03&end_date=2023-01-03T09:00", format="json", HTTP_AUTHORIZATION=token + ) assert response.status_code == status.HTTP_200_OK expected_on_call_times = {user1_public_primary_key: 9} diff --git a/engine/apps/public_api/views/schedules.py b/engine/apps/public_api/views/schedules.py index 88b1fe26..53b414dc 100644 --- a/engine/apps/public_api/views/schedules.py +++ b/engine/apps/public_api/views/schedules.py @@ -1,7 +1,5 @@ -import datetime import logging -import pytz from django_filters import rest_framework as filters from rest_framework import status from rest_framework.decorators import action @@ -141,14 +139,8 @@ class OnCallScheduleChannelView(RateLimitHeadersMixin, UpdateSerializerMixin, Mo start_date = serializer.validated_data["start_date"] end_date = serializer.validated_data["end_date"] - days_between_start_and_end = (end_date - start_date).days - datetime_start = datetime.datetime.combine(start_date, datetime.time.min, tzinfo=pytz.UTC) - datetime_end = datetime_start + datetime.timedelta( - days=days_between_start_and_end, hours=23, minutes=59, seconds=59 - ) - - final_schedule_events: ScheduleEvents = schedule.final_events(datetime_start, datetime_end) + final_schedule_events: ScheduleEvents = schedule.final_events(start_date, end_date) logger.info( f"Exporting oncall shifts for schedule {pk} between dates {start_date} and {end_date}. {len(final_schedule_events)} shift events were found." ) @@ -159,8 +151,8 @@ class OnCallScheduleChannelView(RateLimitHeadersMixin, UpdateSerializerMixin, Mo "user_email": user["email"], "user_username": user["display_name"], # truncate shift start/end exceeding the requested period - "shift_start": event["start"] if event["start"] >= datetime_start else datetime_start, - "shift_end": event["end"] if event["end"] <= datetime_end else datetime_end, + "shift_start": event["start"] if event["start"] >= start_date else start_date, + "shift_end": event["end"] if event["end"] <= end_date else end_date, } for event in final_schedule_events for user in event["users"]