Skip to content

Commit 22c644e

Browse files
authored
Merge pull request #4820 from grafana/dev
v1.8.11
2 parents 49a4272 + a1c67cd commit 22c644e

File tree

50 files changed

+1345
-710
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1345
-710
lines changed

.github/actions/setup-python/action.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ runs:
2323
if: ${{ inputs.install-dependencies == 'true' }}
2424
shell: bash
2525
run: |
26-
pip install uv
26+
pip install uv setuptools
2727
uv pip sync --system ${{ inputs.python-requirements-paths }}

.github/workflows/linting-and-tests.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55

66
env:
77
DJANGO_SETTINGS_MODULE: settings.ci_test
8+
SKIP_SLACK_SDK_WARNING: True
89
DATABASE_HOST: localhost
910
RABBITMQ_URI: amqp://rabbitmq:rabbitmq@localhost:5672
1011
SLACK_CLIENT_OAUTH_ID: 1

dev/kind-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ registry: ctlptl-registry
66
kindV1Alpha4Cluster:
77
nodes:
88
- role: control-plane
9-
image: kindest/node:v1.27.3
9+
image: kindest/node:v1.27.11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
aiohttp==3.9.4
1+
aiohttp==3.10.2
22
Faker==16.4.0
33
tqdm==4.66.3

docs/sources/configure/live-call-routing/index.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ canonical: https://grafana.com/docs/oncall/latest/configure/live-call-routing/
1414
aliases:
1515
- /docs/grafana-cloud/alerting-and-irm/oncall/configure/escalation-chains-and-routes/
1616
- ../live-call-routing/ # /docs/oncall/<ONCALL_VERSION>/escalation-chains-and-routes/
17+
refs:
18+
open-source:
19+
- pattern: /docs/oncall/
20+
destination: /docs/oncall/<ONCALL_VERSION>/set-up/open-source/
21+
- pattern: /docs/grafana-cloud/
22+
destination: /docs/grafana-cloud/alerting-and-irm/oncall/set-up/open-source/
23+
irm-invoice:
24+
- pattern: /docs/grafana-cloud/
25+
destination: /docs/grafana-cloud/cost-management-and-billing/understand-your-invoice/irm-invoice/
1726
---
1827

1928
# Configure SMS & call routing with Grafana OnCall
@@ -27,10 +36,18 @@ You can further customize your configuration to send different alerts to differe
2736

2837
To complete the steps in this guide, ensure you have the following:
2938

30-
- Grafana Cloud account: If you haven't already, [sign up for Grafana Cloud](https://grafana.com/auth/sign-up/create-user).
39+
- For Grafana Cloud users: A Grafana Cloud account. If you haven't already, [sign up for Grafana Cloud](https://grafana.com/auth/sign-up/create-user).
40+
- For OSS users: Notification routing must be configured using either Grafana Cloud or a third-party provider, such as Twilio.
41+
Refer to the [Grafana OnCall open source guide](ref:open-source) for more information.
3142
- Grafana OnCall user with administrator privileges and notification settings configured.
3243
- Twilio account: [Sign up for Twilio](https://www.twilio.com/try-twilio).
3344

45+
{{< admonition type="note" >}}
46+
While OSS users have the option to use Grafana Cloud for phone and SMS routing, it is not required.
47+
If you decide to use Grafana Cloud for notification delivery, be aware that charges may apply.
48+
For more information, refer to our [billing documentation](ref:irm-invoice).
49+
{{< /admonition >}}
50+
3451
## Basic set up
3552

3653
In the basic set up, you'll create an integration in OnCall and configure a phone number in Twilio.

engine/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
2727
&& rm grpcio-1.64.1-cp312-cp312-linux_aarch64.whl; \
2828
fi
2929

30-
RUN pip install uv
30+
RUN pip install uv setuptools
3131

3232
# TODO: figure out how to get this to work.. see comment in .github/workflows/e2e-tests.yml
3333
# https://stackoverflow.com/a/71846527

engine/apps/api/serializers/alert_receive_channel.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
from jinja2 import TemplateSyntaxError
99
from rest_framework import serializers
1010
from rest_framework.exceptions import ValidationError
11-
from rest_framework.fields import SerializerMethodField, set_value
11+
from rest_framework.fields import SerializerMethodField
1212

1313
from apps.alerts.grafana_alerting_sync_manager.grafana_alerting_sync import GrafanaAlertingSyncManager
1414
from apps.alerts.models import AlertReceiveChannel
15-
from apps.alerts.models.channel_filter import ChannelFilter
1615
from apps.base.messaging import get_messaging_backends
1716
from apps.integrations.legacy_prefix import has_legacy_prefix
1817
from apps.labels.models import LabelKeyCache, LabelValueCache
@@ -277,7 +276,7 @@ class AlertReceiveChannelSerializer(
277276
# With using of select_related ORM builds strange join
278277
# which leads to incorrect heartbeat-alert_receive_channel binding in result
279278
PREFETCH_RELATED = ["channel_filters", "integration_heartbeat", "labels", "labels__key", "labels__value"]
280-
SELECT_RELATED = ["organization", "author"]
279+
SELECT_RELATED = ["organization", "author", "team"]
281280

282281
class Meta:
283282
model = AlertReceiveChannel
@@ -490,11 +489,12 @@ def get_is_legacy(self, obj: "AlertReceiveChannel") -> bool:
490489
return has_legacy_prefix(obj.integration)
491490

492491
def get_connected_escalations_chains_count(self, obj: "AlertReceiveChannel") -> int:
493-
return (
494-
ChannelFilter.objects.filter(alert_receive_channel=obj, escalation_chain__isnull=False)
495-
.values("escalation_chain")
496-
.distinct()
497-
.count()
492+
return len(
493+
set(
494+
channel_filter.escalation_chain_id
495+
for channel_filter in obj.channel_filters.all()
496+
if channel_filter.escalation_chain_id is not None
497+
)
498498
)
499499

500500

@@ -632,7 +632,7 @@ def _handle_messaging_backend_updates(self, data, ret):
632632
backend_updates[field] = value
633633
# update backend templates
634634
backend_templates.update(backend_updates)
635-
set_value(ret, ["messaging_backends_templates", backend_id], backend_templates)
635+
self.set_value(ret, ["messaging_backends_templates", backend_id], backend_templates)
636636

637637
return errors
638638

@@ -651,7 +651,7 @@ def _handle_core_template_updates(self, data, ret):
651651
errors[field_name] = "invalid template"
652652
except DjangoValidationError:
653653
errors[field_name] = "invalid URL"
654-
set_value(ret, [field_name], value)
654+
self.set_value(ret, [field_name], value)
655655
return errors
656656

657657
def to_representation(self, obj: "AlertReceiveChannel"):

engine/apps/api/tests/conftest.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from datetime import timedelta
2-
31
import pytest
42
from django.utils import timezone
53

@@ -29,8 +27,8 @@ def _make_alert_groups_all_statuses(alert_receive_channel, channel_filter, alert
2927
resolved_alert_group = make_alert_group(
3028
alert_receive_channel,
3129
channel_filter=channel_filter,
32-
acknowledged_at=timezone.now() + timedelta(hours=1),
33-
resolved_at=timezone.now() + timedelta(hours=2),
30+
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
31+
resolved_at=timezone.now() + timezone.timedelta(hours=2),
3432
resolved=True,
3533
acknowledged=True,
3634
)
@@ -39,7 +37,7 @@ def _make_alert_groups_all_statuses(alert_receive_channel, channel_filter, alert
3937
ack_alert_group = make_alert_group(
4038
alert_receive_channel,
4139
channel_filter=channel_filter,
42-
acknowledged_at=timezone.now() + timedelta(hours=1),
40+
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
4341
acknowledged=True,
4442
)
4543
make_alert(alert_group=ack_alert_group, raw_request_data=alert_raw_request_data)
@@ -51,7 +49,7 @@ def _make_alert_groups_all_statuses(alert_receive_channel, channel_filter, alert
5149
alert_receive_channel,
5250
channel_filter=channel_filter,
5351
silenced=True,
54-
silenced_at=timezone.now() + timedelta(hours=1),
52+
silenced_at=timezone.now() + timezone.timedelta(hours=1),
5553
)
5654
make_alert(alert_group=silenced_alert_group, raw_request_data=alert_raw_request_data)
5755

engine/apps/api/tests/test_alert_group.py

+17-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import datetime
21
from unittest.mock import Mock, patch
32

43
import pytest
@@ -250,8 +249,8 @@ def test_get_filter_resolved_by(
250249
resolved_alert_group = make_alert_group(
251250
alert_receive_channel,
252251
channel_filter=default_channel_filter,
253-
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
254-
resolved_at=timezone.now() + datetime.timedelta(hours=2),
252+
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
253+
resolved_at=timezone.now() + timezone.timedelta(hours=2),
255254
resolved=True,
256255
acknowledged=True,
257256
resolved_by_user=first_user,
@@ -302,8 +301,8 @@ def make_resolved_by_user_alert_group(user):
302301
resolved_alert_group = make_alert_group(
303302
alert_receive_channel,
304303
channel_filter=default_channel_filter,
305-
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
306-
resolved_at=timezone.now() + datetime.timedelta(hours=2),
304+
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
305+
resolved_at=timezone.now() + timezone.timedelta(hours=2),
307306
resolved=True,
308307
acknowledged=True,
309308
resolved_by_user=user,
@@ -348,8 +347,8 @@ def test_get_filter_acknowledged_by(
348347
acknowledged_alert_group = make_alert_group(
349348
alert_receive_channel,
350349
channel_filter=default_channel_filter,
351-
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
352-
resolved_at=timezone.now() + datetime.timedelta(hours=2),
350+
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
351+
resolved_at=timezone.now() + timezone.timedelta(hours=2),
353352
acknowledged=True,
354353
acknowledged_by_user=first_user,
355354
)
@@ -398,8 +397,8 @@ def make_acknowledged_by_user_alert_group(user):
398397
acknowledged_alert_group = make_alert_group(
399398
alert_receive_channel,
400399
channel_filter=default_channel_filter,
401-
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
402-
resolved_at=timezone.now() + datetime.timedelta(hours=2),
400+
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
401+
resolved_at=timezone.now() + timezone.timedelta(hours=2),
403402
acknowledged=True,
404403
acknowledged_by_user=user,
405404
)
@@ -442,7 +441,7 @@ def test_get_filter_silenced_by(
442441
silenced_alert_group = make_alert_group(
443442
alert_receive_channel,
444443
channel_filter=default_channel_filter,
445-
silenced_at=timezone.now() + datetime.timedelta(hours=1),
444+
silenced_at=timezone.now() + timezone.timedelta(hours=1),
446445
silenced=True,
447446
silenced_by_user=first_user,
448447
)
@@ -491,7 +490,7 @@ def make_silenced_by_user_alert_group(user):
491490
acknowledged_alert_group = make_alert_group(
492491
alert_receive_channel,
493492
channel_filter=default_channel_filter,
494-
silenced_at=timezone.now() + datetime.timedelta(hours=1),
493+
silenced_at=timezone.now() + timezone.timedelta(hours=1),
495494
silenced=True,
496495
silenced_by_user=user,
497496
)
@@ -670,8 +669,8 @@ def test_get_filter_mine(
670669
acknowledged_alert_group = make_alert_group(
671670
alert_receive_channel,
672671
channel_filter=default_channel_filter,
673-
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
674-
resolved_at=timezone.now() + datetime.timedelta(hours=2),
672+
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
673+
resolved_at=timezone.now() + timezone.timedelta(hours=2),
675674
acknowledged=True,
676675
acknowledged_by_user=first_user,
677676
)
@@ -724,8 +723,8 @@ def test_get_filter_involved_users(
724723
acknowledged_alert_group = make_alert_group(
725724
alert_receive_channel,
726725
channel_filter=default_channel_filter,
727-
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
728-
resolved_at=timezone.now() + datetime.timedelta(hours=2),
726+
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
727+
resolved_at=timezone.now() + timezone.timedelta(hours=2),
729728
acknowledged=True,
730729
acknowledged_by_user=first_user,
731730
)
@@ -999,7 +998,7 @@ def test_get_title_search(
999998
alert_receive_channel, channel_filter=channel_filter, web_title_cache=f"testing {i+1}"
1000999
)
10011000
# alert groups starting every months going back
1002-
alert_group.started_at = timezone.now() - datetime.timedelta(days=10 + 30 * i)
1001+
alert_group.started_at = timezone.now() - timezone.timedelta(days=10 + 30 * i)
10031002
alert_group.save(update_fields=["started_at"])
10041003
make_alert(alert_group=alert_group, raw_request_data=alert_raw_request_data)
10051004
alert_groups.append(alert_group)
@@ -1021,8 +1020,8 @@ def test_get_title_search(
10211020
response = client.get(
10221021
url
10231022
+ "?search=testing&started_at={}_{}".format(
1024-
(timezone.now() - datetime.timedelta(days=500)).strftime(DateRangeFilterMixin.DATE_FORMAT),
1025-
(timezone.now() - datetime.timedelta(days=30)).strftime(DateRangeFilterMixin.DATE_FORMAT),
1023+
(timezone.now() - timezone.timedelta(days=500)).strftime(DateRangeFilterMixin.DATE_FORMAT),
1024+
(timezone.now() - timezone.timedelta(days=30)).strftime(DateRangeFilterMixin.DATE_FORMAT),
10261025
),
10271026
format="json",
10281027
**make_user_auth_headers(user, token),

engine/apps/api/views/alert_receive_channel.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def get_queryset(self, eager=True, ignore_filtering_by_available_teams=False):
242242
)
243243

244244
# distinct to remove duplicates after alert_receive_channels X labels join
245-
queryset = queryset.distinct()
245+
queryset = queryset.distinct().order_by("id")
246246

247247
return queryset
248248

engine/apps/api/views/shift_swap.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ def get_serializer_class(self):
6565
return ShiftSwapRequestListSerializer if self.action == "list" else super().get_serializer_class()
6666

6767
def get_queryset(self):
68-
queryset = ShiftSwapRequest.objects.filter(schedule__organization=self.request.auth.organization)
68+
queryset = ShiftSwapRequest.objects.filter(schedule__organization=self.request.auth.organization).order_by(
69+
"-created_at"
70+
)
6971
return self.serializer_class.setup_eager_loading(queryset)
7072

7173
def perform_destroy(self, instance: ShiftSwapRequest) -> None:

engine/apps/email/tests/test_inbound_email.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
],
2121
)
2222
@pytest.mark.django_db
23+
@pytest.mark.filterwarnings("ignore:::anymail.*") # ignore missing WEBHOOK_SECRET in amazon ses test setup
2324
def test_amazon_ses_provider_load(
2425
settings, make_organization_and_user_with_token, make_alert_receive_channel, recipients, expected
2526
):
@@ -128,7 +129,10 @@ def test_mailgun_provider_load(
128129
"sender_value,expected_result",
129130
[
130131
("'Alex Smith' <[email protected]>", "[email protected]"),
131-
("'Alex Smith' via [TEST] mail <[email protected]>", "'Alex Smith' via [TEST] mail <[email protected]>"),
132+
# double quotes required when including special characters
133+
("\"'Alex Smith' via [TEST] mail\" <[email protected]>", "[email protected]"),
134+
# missing double quotes
135+
("'Alex Smith' via [TEST] mail <[email protected]>", "\"'Alex Smith' via\""),
132136
],
133137
)
134138
def test_get_sender_from_email_message(sender_value, expected_result):

engine/apps/google/tasks.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,5 @@ def sync_out_of_office_calendar_events_for_user(google_oauth2_user_pk: int) -> N
118118

119119
@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True)
120120
def sync_out_of_office_calendar_events_for_all_users() -> None:
121-
for google_oauth2_user in GoogleOAuth2User.objects.all():
121+
for google_oauth2_user in GoogleOAuth2User.objects.filter(user__organization__deleted_at__isnull=True):
122122
sync_out_of_office_calendar_events_for_user.apply_async(args=(google_oauth2_user.pk,))

engine/apps/google/tests/test_sync_out_of_office_calendar_events_for_user.py

+18
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,21 @@ def _fetch_shift_swap_requests():
372372
ssrs.first().delete()
373373
tasks.sync_out_of_office_calendar_events_for_user(google_oauth2_user_pk)
374374
assert _fetch_shift_swap_requests().count() == 1
375+
376+
377+
@patch("apps.google.tasks.sync_out_of_office_calendar_events_for_user.apply_async")
378+
@pytest.mark.django_db
379+
def test_sync_out_of_office_calendar_events_for_all_users(
380+
mock_sync_out_of_office_calendar_events_for_user,
381+
make_organization_and_user,
382+
make_google_oauth2_user_for_user,
383+
):
384+
organization, user = make_organization_and_user()
385+
google_oauth2_user = make_google_oauth2_user_for_user(user)
386+
387+
deleted_organization, deleted_user = make_organization_and_user()
388+
make_google_oauth2_user_for_user(deleted_user)
389+
deleted_organization.delete()
390+
391+
tasks.sync_out_of_office_calendar_events_for_all_users()
392+
mock_sync_out_of_office_calendar_events_for_user.assert_called_once_with(args=(google_oauth2_user.pk,))

0 commit comments

Comments
 (0)