add support for datetime on final_shifts API parameters (#3103)
# What this PR does - Changes the data type from `DateField` to `DateTimeField` on the `final_shifts` API endpoint - Accepts either a date or a datetime for the `start_date` and `end_date` parameters (e.g. 2021-01-01 or 2021-01-01T01:00) - Datetime granularity is in line with what is configurable on the schedule i.e. `YYYY-MM-DD hh:mm` - removes the rounding logic that modifies the query sent on the database ## Which issue(s) this PR fixes #3086 ## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
This commit is contained in:
parent
d49ccef8cb
commit
c281484f67
4 changed files with 40 additions and 18 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]:
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue