Allow empty users when previewing a web schedule shift
This commit is contained in:
parent
2a058b4acc
commit
37eefe8e08
5 changed files with 167 additions and 9 deletions
|
|
@ -1313,6 +1313,90 @@ def test_on_call_shift_preview(
|
|||
assert returned_events == expected_events
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_on_call_shift_preview_without_users(
|
||||
make_organization_and_user_with_plugin_token,
|
||||
make_user_for_organization,
|
||||
make_user_auth_headers,
|
||||
make_schedule,
|
||||
):
|
||||
organization, user, token = make_organization_and_user_with_plugin_token()
|
||||
client = APIClient()
|
||||
|
||||
schedule = make_schedule(
|
||||
organization,
|
||||
schedule_class=OnCallScheduleWeb,
|
||||
name="test_web_schedule",
|
||||
)
|
||||
|
||||
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_date = now - timezone.timedelta(days=7)
|
||||
request_date = start_date
|
||||
user = make_user_for_organization(organization)
|
||||
|
||||
url = "{}?date={}&days={}".format(
|
||||
reverse("api-internal:oncall_shifts-preview"), request_date.strftime("%Y-%m-%d"), 1
|
||||
)
|
||||
shift_start = (start_date + timezone.timedelta(hours=12)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
shift_end = (start_date + timezone.timedelta(hours=13)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
shift_data = {
|
||||
"schedule": schedule.public_primary_key,
|
||||
"type": CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
|
||||
"rotation_start": shift_start,
|
||||
"shift_start": shift_start,
|
||||
"shift_end": shift_end,
|
||||
# passing empty users
|
||||
"rolling_users": [],
|
||||
"priority_level": 2,
|
||||
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
|
||||
}
|
||||
response = client.post(url, shift_data, format="json", **make_user_auth_headers(user, token))
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
# check rotation events
|
||||
rotation_events = response.json()["rotation"]
|
||||
expected_rotation_events = [
|
||||
{
|
||||
"calendar_type": OnCallSchedule.TYPE_ICAL_PRIMARY,
|
||||
"start": shift_start,
|
||||
"end": shift_end,
|
||||
"all_day": False,
|
||||
"is_override": False,
|
||||
"is_empty": True,
|
||||
"is_gap": False,
|
||||
"priority_level": None,
|
||||
"missing_users": [],
|
||||
"users": [],
|
||||
"source": "web",
|
||||
}
|
||||
]
|
||||
# there isn't a saved shift, we don't care/know the temp pk
|
||||
_ = [r.pop("shift") for r in rotation_events]
|
||||
assert rotation_events == expected_rotation_events
|
||||
|
||||
# check final schedule events
|
||||
final_events = response.json()["final"]
|
||||
expected_events = [
|
||||
{
|
||||
"end": shift_end,
|
||||
"start": shift_start,
|
||||
"user": None,
|
||||
"is_empty": True,
|
||||
}
|
||||
]
|
||||
returned_events = [
|
||||
{
|
||||
"end": e["end"],
|
||||
"start": e["start"],
|
||||
"user": e["users"][0]["display_name"] if e["users"] else None,
|
||||
"is_empty": e["is_empty"],
|
||||
}
|
||||
for e in final_events
|
||||
if not e["is_override"] and not e["is_gap"]
|
||||
]
|
||||
assert returned_events == expected_events
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_on_call_shift_preview_merge_events(
|
||||
make_organization_and_user_with_plugin_token,
|
||||
|
|
|
|||
|
|
@ -89,8 +89,6 @@ class OnCallShiftView(PublicPrimaryKeyMixin, UpdateSerializerMixin, ModelViewSet
|
|||
validated_data = serializer._correct_validated_data(
|
||||
serializer.validated_data["type"], serializer.validated_data
|
||||
)
|
||||
if not validated_data.get("rolling_users"):
|
||||
return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
updated_shift_pk = self.request.data.get("shift_pk")
|
||||
shift = CustomOnCallShift(**validated_data)
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ class CustomOnCallShift(models.Model):
|
|||
|
||||
return is_finished
|
||||
|
||||
def convert_to_ical(self, time_zone="UTC"):
|
||||
def convert_to_ical(self, time_zone="UTC", allow_empty_users=False):
|
||||
result = ""
|
||||
# use shift time_zone if it exists, otherwise use schedule or default time_zone
|
||||
time_zone = self.time_zone if self.time_zone is not None else time_zone
|
||||
|
|
@ -285,8 +285,10 @@ class CustomOnCallShift(models.Model):
|
|||
all_rotation_checked = False
|
||||
|
||||
users_queue = self.get_rolling_users()
|
||||
if not users_queue:
|
||||
if not users_queue and not allow_empty_users:
|
||||
return result
|
||||
if not users_queue and allow_empty_users:
|
||||
users_queue = [[None]]
|
||||
if self.frequency is None:
|
||||
users_queue = users_queue[:1]
|
||||
|
||||
|
|
@ -354,7 +356,8 @@ class CustomOnCallShift(models.Model):
|
|||
current_event = Event.from_ical(event_ical)
|
||||
# take shift interval, not event interval. For rolling_users shift it is not the same.
|
||||
interval = self.interval or 1
|
||||
current_event["rrule"]["INTERVAL"] = interval
|
||||
if "rrule" in current_event:
|
||||
current_event["rrule"]["INTERVAL"] = interval
|
||||
current_event_start = current_event["DTSTART"].dt
|
||||
next_event_start = current_event_start
|
||||
# Calculate the minimum start date for the next event based on rotation frequency. We don't need to do this
|
||||
|
|
@ -482,7 +485,8 @@ class CustomOnCallShift(models.Model):
|
|||
rolling_users = self.rolling_users
|
||||
for users_dict in rolling_users:
|
||||
users_list = list(users.filter(pk__in=users_dict.keys()))
|
||||
users_queue.append(users_list)
|
||||
if users_list:
|
||||
users_queue.append(users_list)
|
||||
return users_queue
|
||||
|
||||
def add_rolling_users(self, rolling_users_list):
|
||||
|
|
|
|||
|
|
@ -571,7 +571,7 @@ class OnCallScheduleCalendar(OnCallSchedule):
|
|||
class OnCallScheduleWeb(OnCallSchedule):
|
||||
time_zone = models.CharField(max_length=100, default="UTC")
|
||||
|
||||
def _generate_ical_file_from_shifts(self, qs, extra_shifts=None):
|
||||
def _generate_ical_file_from_shifts(self, qs, extra_shifts=None, allow_empty_users=False):
|
||||
"""Generate iCal events file from custom on-call shifts."""
|
||||
ical = None
|
||||
if qs.exists() or extra_shifts is not None:
|
||||
|
|
@ -586,7 +586,7 @@ class OnCallScheduleWeb(OnCallSchedule):
|
|||
ical = ical_file.replace(end_line, "").strip()
|
||||
ical = f"{ical}\r\n"
|
||||
for event in itertools.chain(qs.all(), extra_shifts):
|
||||
ical += event.convert_to_ical(self.time_zone)
|
||||
ical += event.convert_to_ical(self.time_zone, allow_empty_users=allow_empty_users)
|
||||
ical += f"{end_line}\r\n"
|
||||
return ical
|
||||
|
||||
|
|
@ -657,7 +657,7 @@ class OnCallScheduleWeb(OnCallSchedule):
|
|||
custom_shift.public_primary_key = updated_shift_pk
|
||||
qs = qs.exclude(public_primary_key=updated_shift_pk)
|
||||
|
||||
ical_file = self._generate_ical_file_from_shifts(qs, extra_shifts=extra_shifts)
|
||||
ical_file = self._generate_ical_file_from_shifts(qs, extra_shifts=extra_shifts, allow_empty_users=True)
|
||||
|
||||
original_value = getattr(self, ical_attr)
|
||||
_invalidate_cache(self, ical_property)
|
||||
|
|
|
|||
|
|
@ -552,6 +552,78 @@ def test_preview_shift(make_organization, make_user_for_organization, make_sched
|
|||
assert schedule._ical_file_primary == schedule_primary_ical
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_preview_shift_no_user(make_organization, make_user_for_organization, make_schedule, make_on_call_shift):
|
||||
organization = make_organization()
|
||||
schedule = make_schedule(
|
||||
organization,
|
||||
schedule_class=OnCallScheduleWeb,
|
||||
name="test_web_schedule",
|
||||
)
|
||||
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_date = now - timezone.timedelta(days=7)
|
||||
|
||||
schedule_primary_ical = schedule._ical_file_primary
|
||||
|
||||
# proposed shift
|
||||
new_shift = CustomOnCallShift(
|
||||
type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
|
||||
organization=organization,
|
||||
schedule=schedule,
|
||||
name="testing",
|
||||
start=start_date + timezone.timedelta(hours=12),
|
||||
rotation_start=start_date + timezone.timedelta(hours=12),
|
||||
duration=timezone.timedelta(seconds=3600),
|
||||
frequency=CustomOnCallShift.FREQUENCY_DAILY,
|
||||
priority_level=2,
|
||||
rolling_users=[],
|
||||
)
|
||||
|
||||
rotation_events, final_events = schedule.preview_shift(new_shift, "UTC", start_date, days=1)
|
||||
|
||||
# check rotation events
|
||||
expected_rotation_events = [
|
||||
{
|
||||
"calendar_type": OnCallSchedule.TYPE_ICAL_PRIMARY,
|
||||
"start": new_shift.start,
|
||||
"end": new_shift.start + new_shift.duration,
|
||||
"all_day": False,
|
||||
"is_override": False,
|
||||
"is_empty": True,
|
||||
"is_gap": False,
|
||||
"priority_level": None,
|
||||
"missing_users": [],
|
||||
"users": [],
|
||||
"shift": {"pk": new_shift.public_primary_key},
|
||||
"source": "api",
|
||||
}
|
||||
]
|
||||
assert rotation_events == expected_rotation_events
|
||||
|
||||
expected_events = [
|
||||
{
|
||||
"end": new_shift.start + new_shift.duration,
|
||||
"start": new_shift.start,
|
||||
"user": None,
|
||||
"is_empty": True,
|
||||
}
|
||||
]
|
||||
returned_events = [
|
||||
{
|
||||
"end": e["end"],
|
||||
"start": e["start"],
|
||||
"user": e["users"][0]["display_name"] if e["users"] else None,
|
||||
"is_empty": e["is_empty"],
|
||||
}
|
||||
for e in final_events
|
||||
if not e["is_override"] and not e["is_gap"]
|
||||
]
|
||||
assert returned_events == expected_events
|
||||
|
||||
# final ical schedule didn't change
|
||||
assert schedule._ical_file_primary == schedule_primary_ical
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_preview_override_shift(make_organization, make_user_for_organization, make_schedule, make_on_call_shift):
|
||||
organization = make_organization()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue