Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for datetime on final_shifts API parameters #3103

Merged
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions engine/apps/public_api/serializers/schedules_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ def to_representation(self, instance):


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"]:
Expand Down
35 changes: 30 additions & 5 deletions engine/apps/public_api/tests/test_schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -917,6 +917,23 @@ def _make_request(schedule, query_params=""):
]
}

# 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(
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for these tests, do you mind parameterizing them to test both formats? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added separate tests for validating the parameter formats. Let me know if i missed the mark on what you were asking.

This change essentially means that specifying a date only will result in a report that starts and ends at 00:00 for the time.

Expand Down
14 changes: 3 additions & 11 deletions engine/apps/public_api/views/schedules.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -141,14 +139,8 @@ def final_shifts(self, request, pk):

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."
)
Expand All @@ -159,8 +151,8 @@ def final_shifts(self, request, pk):
"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"]
Expand Down