Skip to content

Commit c358586

Browse files
authored
Merge pull request #291 from grafana/dev
Merge dev to main
2 parents 4520f9f + 879e157 commit c358586

21 files changed

+441
-37
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Change Log
22

3+
## v1.0.12 (2022-07-26)
4+
- Update push-notifications dependency
5+
- Rework how absolute URLs are built
6+
- Fix to show maintenance windows per team
7+
- Logging improvements
8+
- Internal api to get a schedule final events
9+
310
## v1.0.10 (2022-07-22)
411
- Speed-up of alert group web caching
512
- Internal api for OnCall shifts

engine/apps/alerts/tests/test_alert_receiver_channel.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.urls import reverse
66

77
from apps.alerts.models import AlertReceiveChannel
8+
from common.api_helpers.utils import create_engine_url
89

910

1011
@pytest.mark.django_db
@@ -26,11 +27,7 @@ def test_integration_url(make_organization, make_alert_receive_channel, url, set
2627
kwargs={"alert_channel_key": alert_receive_channel.token},
2728
)
2829

29-
# remove trailing / if present
30-
if url[-1] == "/":
31-
url = url[:-1]
32-
33-
assert alert_receive_channel.integration_url == f"{url}{path}"
30+
assert alert_receive_channel.integration_url == create_engine_url(path)
3431

3532

3633
@pytest.mark.django_db

engine/apps/api/tests/test_schedules.py

+228-5
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,13 @@ def test_filter_events_calendar(
469469
"by_day": ["MO", "FR"],
470470
"schedule": schedule,
471471
}
472-
473472
on_call_shift = make_on_call_shift(
474473
organization=organization, shift_type=CustomOnCallShift.TYPE_RECURRENT_EVENT, **data
475474
)
476475
on_call_shift.users.add(user)
477476

478477
url = reverse("api-internal:schedule-filter-events", kwargs={"pk": schedule.public_primary_key})
478+
url += "?type=rotation"
479479
response = client.get(url, format="json", **make_user_auth_headers(user, token))
480480

481481
# current week events are expected
@@ -525,6 +525,7 @@ def test_filter_events_calendar(
525525
@pytest.mark.django_db
526526
def test_filter_events_range_calendar(
527527
make_organization_and_user_with_plugin_token,
528+
make_user_for_organization,
528529
make_user_auth_headers,
529530
make_schedule,
530531
make_on_call_shift,
@@ -540,6 +541,9 @@ def test_filter_events_range_calendar(
540541

541542
now = timezone.now().replace(microsecond=0)
542543
start_date = now - timezone.timedelta(days=7)
544+
mon_start = now - timezone.timedelta(days=start_date.weekday())
545+
request_date = mon_start + timezone.timedelta(days=2)
546+
543547
data = {
544548
"start": start_date,
545549
"rotation_start": start_date,
@@ -549,17 +553,27 @@ def test_filter_events_range_calendar(
549553
"by_day": ["MO", "FR"],
550554
"schedule": schedule,
551555
}
552-
553556
on_call_shift = make_on_call_shift(
554557
organization=organization, shift_type=CustomOnCallShift.TYPE_RECURRENT_EVENT, **data
555558
)
556559
on_call_shift.users.add(user)
557560

558-
mon_start = now - timezone.timedelta(days=start_date.weekday())
559-
request_date = mon_start + timezone.timedelta(days=2)
561+
# add override shift
562+
override_start = request_date + timezone.timedelta(seconds=3600)
563+
override_data = {
564+
"start": override_start,
565+
"rotation_start": override_start,
566+
"duration": timezone.timedelta(seconds=3600),
567+
"schedule": schedule,
568+
}
569+
override = make_on_call_shift(
570+
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
571+
)
572+
other_user = make_user_for_organization(organization)
573+
override.users.add(other_user)
560574

561575
url = reverse("api-internal:schedule-filter-events", kwargs={"pk": schedule.public_primary_key})
562-
url += "?date={}&days=3".format(request_date.strftime("%Y-%m-%d"))
576+
url += "?date={}&days=3&type=rotation".format(request_date.strftime("%Y-%m-%d"))
563577
response = client.get(url, format="json", **make_user_auth_headers(user, token))
564578

565579
# only friday occurrence is expected
@@ -590,6 +604,215 @@ def test_filter_events_range_calendar(
590604
assert response.data == expected_result
591605

592606

607+
@pytest.mark.django_db
608+
def test_filter_events_overrides(
609+
make_organization_and_user_with_plugin_token,
610+
make_user_for_organization,
611+
make_user_auth_headers,
612+
make_schedule,
613+
make_on_call_shift,
614+
):
615+
organization, user, token = make_organization_and_user_with_plugin_token()
616+
client = APIClient()
617+
618+
schedule = make_schedule(
619+
organization,
620+
schedule_class=OnCallScheduleWeb,
621+
name="test_web_schedule",
622+
)
623+
624+
now = timezone.now().replace(microsecond=0)
625+
start_date = now - timezone.timedelta(days=7)
626+
mon_start = now - timezone.timedelta(days=start_date.weekday())
627+
request_date = mon_start + timezone.timedelta(days=2)
628+
629+
data = {
630+
"start": start_date,
631+
"rotation_start": start_date,
632+
"duration": timezone.timedelta(seconds=7200),
633+
"priority_level": 1,
634+
"frequency": CustomOnCallShift.FREQUENCY_WEEKLY,
635+
"by_day": ["MO", "FR"],
636+
"schedule": schedule,
637+
}
638+
on_call_shift = make_on_call_shift(
639+
organization=organization, shift_type=CustomOnCallShift.TYPE_RECURRENT_EVENT, **data
640+
)
641+
on_call_shift.users.add(user)
642+
643+
# add override shift
644+
override_start = request_date + timezone.timedelta(seconds=3600)
645+
override_data = {
646+
"start": override_start,
647+
"rotation_start": override_start,
648+
"duration": timezone.timedelta(seconds=3600),
649+
"schedule": schedule,
650+
}
651+
override = make_on_call_shift(
652+
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
653+
)
654+
other_user = make_user_for_organization(organization)
655+
override.add_rolling_users([[other_user]])
656+
657+
url = reverse("api-internal:schedule-filter-events", kwargs={"pk": schedule.public_primary_key})
658+
url += "?date={}&days=3&type=override".format(request_date.strftime("%Y-%m-%d"))
659+
response = client.get(url, format="json", **make_user_auth_headers(user, token))
660+
661+
# only override occurrence is expected
662+
expected_result = {
663+
"id": schedule.public_primary_key,
664+
"name": "test_web_schedule",
665+
"type": 2,
666+
"events": [
667+
{
668+
"all_day": False,
669+
"start": override_start,
670+
"end": override_start + override.duration,
671+
"users": [{"display_name": other_user.username, "pk": other_user.public_primary_key}],
672+
"missing_users": [],
673+
"priority_level": None,
674+
"source": "api",
675+
"calendar_type": OnCallSchedule.OVERRIDES,
676+
"is_empty": False,
677+
"is_gap": False,
678+
"shift": {
679+
"pk": override.public_primary_key,
680+
},
681+
}
682+
],
683+
}
684+
assert response.status_code == status.HTTP_200_OK
685+
assert response.data == expected_result
686+
687+
688+
@pytest.mark.django_db
689+
def test_filter_events_final_schedule(
690+
make_organization_and_user_with_plugin_token,
691+
make_user_for_organization,
692+
make_user_auth_headers,
693+
make_schedule,
694+
make_on_call_shift,
695+
):
696+
organization, user, token = make_organization_and_user_with_plugin_token()
697+
client = APIClient()
698+
699+
schedule = make_schedule(
700+
organization,
701+
schedule_class=OnCallScheduleWeb,
702+
name="test_web_schedule",
703+
)
704+
705+
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
706+
start_date = now - timezone.timedelta(days=7)
707+
request_date = start_date
708+
709+
user_a, user_b, user_c, user_d, user_e = (make_user_for_organization(organization, username=i) for i in "ABCDE")
710+
711+
shifts = (
712+
# user, priority, start time (h), duration (hs)
713+
(user_a, 1, 10, 5), # r1-1: 10-15 / A
714+
(user_b, 1, 11, 2), # r1-2: 11-13 / B
715+
(user_a, 1, 16, 3), # r1-3: 16-19 / A
716+
(user_a, 1, 21, 1), # r1-4: 21-22 / A
717+
(user_b, 1, 22, 2), # r1-5: 22-00 / B
718+
(user_c, 2, 12, 2), # r2-1: 12-14 / C
719+
(user_d, 2, 14, 1), # r2-2: 14-15 / D
720+
(user_d, 2, 17, 1), # r2-3: 17-18 / D
721+
(user_d, 2, 20, 3), # r2-4: 20-23 / D
722+
)
723+
for user, priority, start_h, duration in shifts:
724+
data = {
725+
"start": start_date + timezone.timedelta(hours=start_h),
726+
"rotation_start": start_date,
727+
"duration": timezone.timedelta(hours=duration),
728+
"priority_level": priority,
729+
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
730+
"schedule": schedule,
731+
}
732+
on_call_shift = make_on_call_shift(
733+
organization=organization, shift_type=CustomOnCallShift.TYPE_RECURRENT_EVENT, **data
734+
)
735+
on_call_shift.users.add(user)
736+
737+
# override: 22-23 / E
738+
override_data = {
739+
"start": start_date + timezone.timedelta(hours=22),
740+
"rotation_start": start_date,
741+
"duration": timezone.timedelta(hours=1),
742+
"schedule": schedule,
743+
}
744+
override = make_on_call_shift(
745+
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
746+
)
747+
override.add_rolling_users([[user_e]])
748+
749+
url = reverse("api-internal:schedule-filter-events", kwargs={"pk": schedule.public_primary_key})
750+
url += "?date={}&days=1".format(request_date.strftime("%Y-%m-%d"))
751+
response = client.get(url, format="json", **make_user_auth_headers(user, token))
752+
assert response.status_code == status.HTTP_200_OK
753+
754+
expected = (
755+
# start (h), duration (H), user, priority, is_gap, is_override
756+
(0, 10, None, None, True, False), # 0-10 gap
757+
(10, 2, "A", 1, False, False), # 10-12 A
758+
(11, 1, "B", 1, False, False), # 11-12 B
759+
(12, 2, "C", 2, False, False), # 12-14 C
760+
(14, 1, "D", 2, False, False), # 14-15 D
761+
(15, 1, None, None, True, False), # 15-16 gap
762+
(16, 1, "A", 1, False, False), # 16-17 A
763+
(17, 1, "D", 2, False, False), # 17-18 D
764+
(18, 1, "A", 1, False, False), # 18-19 A
765+
(19, 1, None, None, True, False), # 19-20 gap
766+
(20, 2, "D", 2, False, False), # 20-22 D
767+
(22, 1, "E", None, False, True), # 22-23 E (override)
768+
(23, 1, "B", 1, False, False), # 23-00 B
769+
)
770+
expected_events = [
771+
{
772+
"calendar_type": 1 if is_override else None if is_gap else 0,
773+
"end": start_date + timezone.timedelta(hours=start + duration),
774+
"is_gap": is_gap,
775+
"priority_level": priority,
776+
"start": start_date + timezone.timedelta(hours=start, milliseconds=1 if start == 0 else 0),
777+
"user": user,
778+
}
779+
for start, duration, user, priority, is_gap, is_override in expected
780+
]
781+
returned_events = [
782+
{
783+
"calendar_type": e["calendar_type"],
784+
"end": e["end"],
785+
"is_gap": e["is_gap"],
786+
"priority_level": e["priority_level"],
787+
"start": e["start"],
788+
"user": e["users"][0]["display_name"] if e["users"] else None,
789+
}
790+
for e in response.data["events"]
791+
]
792+
assert returned_events == expected_events
793+
794+
795+
@pytest.mark.django_db
796+
def test_filter_events_invalid_type(
797+
make_organization_and_user_with_plugin_token,
798+
make_user_auth_headers,
799+
make_schedule,
800+
):
801+
organization, user, token = make_organization_and_user_with_plugin_token()
802+
client = APIClient()
803+
804+
schedule = make_schedule(
805+
organization,
806+
schedule_class=OnCallScheduleWeb,
807+
name="test_web_schedule",
808+
)
809+
810+
url = reverse("api-internal:schedule-filter-events", kwargs={"pk": schedule.public_primary_key})
811+
url += "?type=invalid"
812+
response = client.get(url, format="json", **make_user_auth_headers(user, token))
813+
assert response.status_code == status.HTTP_400_BAD_REQUEST
814+
815+
593816
@pytest.mark.django_db
594817
@pytest.mark.parametrize(
595818
"role,expected_status",

engine/apps/api/urls.py

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from .views.subscription import SubscriptionView
4040
from .views.team import TeamViewSet
4141
from .views.telegram_channels import TelegramChannelViewSet
42+
from .views.test_insight_logs import TestInsightLogsAPIView
4243
from .views.user import CurrentUserView, UserView
4344
from .views.user_group import UserGroupViewSet
4445

@@ -108,6 +109,7 @@
108109
"preview_template_options", PreviewTemplateOptionsView.as_view(), name="preview_template_options"
109110
),
110111
optional_slash_path("route_regex_debugger", RouteRegexDebuggerView.as_view(), name="route_regex_debugger"),
112+
optional_slash_path("insight_logs_test", TestInsightLogsAPIView.as_view(), name="insight-logs-test"),
111113
]
112114

113115
urlpatterns += [

engine/apps/api/views/maintenance.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ class MaintenanceAPIView(APIView):
4343

4444
def get(self, request):
4545
organization = self.request.auth.organization
46+
team = self.request.user.current_team
47+
4648
response = []
4749
integrations_under_maintenance = AlertReceiveChannel.objects.filter(
48-
maintenance_mode__isnull=False, organization=organization
50+
maintenance_mode__isnull=False, organization=organization, team=team
4951
).order_by("maintenance_started_at")
5052

5153
if organization.maintenance_mode is not None:

0 commit comments

Comments
 (0)