Skip to content

Commit

Permalink
Merge pull request #4820 from grafana/dev
Browse files Browse the repository at this point in the history
v1.8.11
  • Loading branch information
vadimkerr authored Aug 14, 2024
2 parents 49a4272 + a1c67cd commit 22c644e
Show file tree
Hide file tree
Showing 50 changed files with 1,345 additions and 710 deletions.
2 changes: 1 addition & 1 deletion .github/actions/setup-python/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ runs:
if: ${{ inputs.install-dependencies == 'true' }}
shell: bash
run: |
pip install uv
pip install uv setuptools
uv pip sync --system ${{ inputs.python-requirements-paths }}
1 change: 1 addition & 0 deletions .github/workflows/linting-and-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:

env:
DJANGO_SETTINGS_MODULE: settings.ci_test
SKIP_SLACK_SDK_WARNING: True
DATABASE_HOST: localhost
RABBITMQ_URI: amqp://rabbitmq:rabbitmq@localhost:5672
SLACK_CLIENT_OAUTH_ID: 1
Expand Down
2 changes: 1 addition & 1 deletion dev/kind-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ registry: ctlptl-registry
kindV1Alpha4Cluster:
nodes:
- role: control-plane
image: kindest/node:v1.27.3
image: kindest/node:v1.27.11
2 changes: 1 addition & 1 deletion dev/scripts/generate-fake-data/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
aiohttp==3.9.4
aiohttp==3.10.2
Faker==16.4.0
tqdm==4.66.3
19 changes: 18 additions & 1 deletion docs/sources/configure/live-call-routing/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ canonical: https://grafana.com/docs/oncall/latest/configure/live-call-routing/
aliases:
- /docs/grafana-cloud/alerting-and-irm/oncall/configure/escalation-chains-and-routes/
- ../live-call-routing/ # /docs/oncall/<ONCALL_VERSION>/escalation-chains-and-routes/
refs:
open-source:
- pattern: /docs/oncall/
destination: /docs/oncall/<ONCALL_VERSION>/set-up/open-source/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/oncall/set-up/open-source/
irm-invoice:
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/cost-management-and-billing/understand-your-invoice/irm-invoice/
---

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

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

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

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

## Basic set up

In the basic set up, you'll create an integration in OnCall and configure a phone number in Twilio.
Expand Down
2 changes: 1 addition & 1 deletion engine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
&& rm grpcio-1.64.1-cp312-cp312-linux_aarch64.whl; \
fi

RUN pip install uv
RUN pip install uv setuptools

# TODO: figure out how to get this to work.. see comment in .github/workflows/e2e-tests.yml
# https://stackoverflow.com/a/71846527
Expand Down
20 changes: 10 additions & 10 deletions engine/apps/api/serializers/alert_receive_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
from jinja2 import TemplateSyntaxError
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.fields import SerializerMethodField, set_value
from rest_framework.fields import SerializerMethodField

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

class Meta:
model = AlertReceiveChannel
Expand Down Expand Up @@ -490,11 +489,12 @@ def get_is_legacy(self, obj: "AlertReceiveChannel") -> bool:
return has_legacy_prefix(obj.integration)

def get_connected_escalations_chains_count(self, obj: "AlertReceiveChannel") -> int:
return (
ChannelFilter.objects.filter(alert_receive_channel=obj, escalation_chain__isnull=False)
.values("escalation_chain")
.distinct()
.count()
return len(
set(
channel_filter.escalation_chain_id
for channel_filter in obj.channel_filters.all()
if channel_filter.escalation_chain_id is not None
)
)


Expand Down Expand Up @@ -632,7 +632,7 @@ def _handle_messaging_backend_updates(self, data, ret):
backend_updates[field] = value
# update backend templates
backend_templates.update(backend_updates)
set_value(ret, ["messaging_backends_templates", backend_id], backend_templates)
self.set_value(ret, ["messaging_backends_templates", backend_id], backend_templates)

return errors

Expand All @@ -651,7 +651,7 @@ def _handle_core_template_updates(self, data, ret):
errors[field_name] = "invalid template"
except DjangoValidationError:
errors[field_name] = "invalid URL"
set_value(ret, [field_name], value)
self.set_value(ret, [field_name], value)
return errors

def to_representation(self, obj: "AlertReceiveChannel"):
Expand Down
10 changes: 4 additions & 6 deletions engine/apps/api/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from datetime import timedelta

import pytest
from django.utils import timezone

Expand Down Expand Up @@ -29,8 +27,8 @@ def _make_alert_groups_all_statuses(alert_receive_channel, channel_filter, alert
resolved_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=channel_filter,
acknowledged_at=timezone.now() + timedelta(hours=1),
resolved_at=timezone.now() + timedelta(hours=2),
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
resolved_at=timezone.now() + timezone.timedelta(hours=2),
resolved=True,
acknowledged=True,
)
Expand All @@ -39,7 +37,7 @@ def _make_alert_groups_all_statuses(alert_receive_channel, channel_filter, alert
ack_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=channel_filter,
acknowledged_at=timezone.now() + timedelta(hours=1),
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
acknowledged=True,
)
make_alert(alert_group=ack_alert_group, raw_request_data=alert_raw_request_data)
Expand All @@ -51,7 +49,7 @@ def _make_alert_groups_all_statuses(alert_receive_channel, channel_filter, alert
alert_receive_channel,
channel_filter=channel_filter,
silenced=True,
silenced_at=timezone.now() + timedelta(hours=1),
silenced_at=timezone.now() + timezone.timedelta(hours=1),
)
make_alert(alert_group=silenced_alert_group, raw_request_data=alert_raw_request_data)

Expand Down
35 changes: 17 additions & 18 deletions engine/apps/api/tests/test_alert_group.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import datetime
from unittest.mock import Mock, patch

import pytest
Expand Down Expand Up @@ -250,8 +249,8 @@ def test_get_filter_resolved_by(
resolved_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=default_channel_filter,
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
resolved_at=timezone.now() + datetime.timedelta(hours=2),
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
resolved_at=timezone.now() + timezone.timedelta(hours=2),
resolved=True,
acknowledged=True,
resolved_by_user=first_user,
Expand Down Expand Up @@ -302,8 +301,8 @@ def make_resolved_by_user_alert_group(user):
resolved_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=default_channel_filter,
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
resolved_at=timezone.now() + datetime.timedelta(hours=2),
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
resolved_at=timezone.now() + timezone.timedelta(hours=2),
resolved=True,
acknowledged=True,
resolved_by_user=user,
Expand Down Expand Up @@ -348,8 +347,8 @@ def test_get_filter_acknowledged_by(
acknowledged_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=default_channel_filter,
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
resolved_at=timezone.now() + datetime.timedelta(hours=2),
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
resolved_at=timezone.now() + timezone.timedelta(hours=2),
acknowledged=True,
acknowledged_by_user=first_user,
)
Expand Down Expand Up @@ -398,8 +397,8 @@ def make_acknowledged_by_user_alert_group(user):
acknowledged_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=default_channel_filter,
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
resolved_at=timezone.now() + datetime.timedelta(hours=2),
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
resolved_at=timezone.now() + timezone.timedelta(hours=2),
acknowledged=True,
acknowledged_by_user=user,
)
Expand Down Expand Up @@ -442,7 +441,7 @@ def test_get_filter_silenced_by(
silenced_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=default_channel_filter,
silenced_at=timezone.now() + datetime.timedelta(hours=1),
silenced_at=timezone.now() + timezone.timedelta(hours=1),
silenced=True,
silenced_by_user=first_user,
)
Expand Down Expand Up @@ -491,7 +490,7 @@ def make_silenced_by_user_alert_group(user):
acknowledged_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=default_channel_filter,
silenced_at=timezone.now() + datetime.timedelta(hours=1),
silenced_at=timezone.now() + timezone.timedelta(hours=1),
silenced=True,
silenced_by_user=user,
)
Expand Down Expand Up @@ -670,8 +669,8 @@ def test_get_filter_mine(
acknowledged_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=default_channel_filter,
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
resolved_at=timezone.now() + datetime.timedelta(hours=2),
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
resolved_at=timezone.now() + timezone.timedelta(hours=2),
acknowledged=True,
acknowledged_by_user=first_user,
)
Expand Down Expand Up @@ -724,8 +723,8 @@ def test_get_filter_involved_users(
acknowledged_alert_group = make_alert_group(
alert_receive_channel,
channel_filter=default_channel_filter,
acknowledged_at=timezone.now() + datetime.timedelta(hours=1),
resolved_at=timezone.now() + datetime.timedelta(hours=2),
acknowledged_at=timezone.now() + timezone.timedelta(hours=1),
resolved_at=timezone.now() + timezone.timedelta(hours=2),
acknowledged=True,
acknowledged_by_user=first_user,
)
Expand Down Expand Up @@ -999,7 +998,7 @@ def test_get_title_search(
alert_receive_channel, channel_filter=channel_filter, web_title_cache=f"testing {i+1}"
)
# alert groups starting every months going back
alert_group.started_at = timezone.now() - datetime.timedelta(days=10 + 30 * i)
alert_group.started_at = timezone.now() - timezone.timedelta(days=10 + 30 * i)
alert_group.save(update_fields=["started_at"])
make_alert(alert_group=alert_group, raw_request_data=alert_raw_request_data)
alert_groups.append(alert_group)
Expand All @@ -1021,8 +1020,8 @@ def test_get_title_search(
response = client.get(
url
+ "?search=testing&started_at={}_{}".format(
(timezone.now() - datetime.timedelta(days=500)).strftime(DateRangeFilterMixin.DATE_FORMAT),
(timezone.now() - datetime.timedelta(days=30)).strftime(DateRangeFilterMixin.DATE_FORMAT),
(timezone.now() - timezone.timedelta(days=500)).strftime(DateRangeFilterMixin.DATE_FORMAT),
(timezone.now() - timezone.timedelta(days=30)).strftime(DateRangeFilterMixin.DATE_FORMAT),
),
format="json",
**make_user_auth_headers(user, token),
Expand Down
2 changes: 1 addition & 1 deletion engine/apps/api/views/alert_receive_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ def get_queryset(self, eager=True, ignore_filtering_by_available_teams=False):
)

# distinct to remove duplicates after alert_receive_channels X labels join
queryset = queryset.distinct()
queryset = queryset.distinct().order_by("id")

return queryset

Expand Down
4 changes: 3 additions & 1 deletion engine/apps/api/views/shift_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ def get_serializer_class(self):
return ShiftSwapRequestListSerializer if self.action == "list" else super().get_serializer_class()

def get_queryset(self):
queryset = ShiftSwapRequest.objects.filter(schedule__organization=self.request.auth.organization)
queryset = ShiftSwapRequest.objects.filter(schedule__organization=self.request.auth.organization).order_by(
"-created_at"
)
return self.serializer_class.setup_eager_loading(queryset)

def perform_destroy(self, instance: ShiftSwapRequest) -> None:
Expand Down
6 changes: 5 additions & 1 deletion engine/apps/email/tests/test_inbound_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
],
)
@pytest.mark.django_db
@pytest.mark.filterwarnings("ignore:::anymail.*") # ignore missing WEBHOOK_SECRET in amazon ses test setup
def test_amazon_ses_provider_load(
settings, make_organization_and_user_with_token, make_alert_receive_channel, recipients, expected
):
Expand Down Expand Up @@ -128,7 +129,10 @@ def test_mailgun_provider_load(
"sender_value,expected_result",
[
("'Alex Smith' <[email protected]>", "[email protected]"),
("'Alex Smith' via [TEST] mail <[email protected]>", "'Alex Smith' via [TEST] mail <[email protected]>"),
# double quotes required when including special characters
("\"'Alex Smith' via [TEST] mail\" <[email protected]>", "[email protected]"),
# missing double quotes
("'Alex Smith' via [TEST] mail <[email protected]>", "\"'Alex Smith' via\""),
],
)
def test_get_sender_from_email_message(sender_value, expected_result):
Expand Down
2 changes: 1 addition & 1 deletion engine/apps/google/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,5 @@ def sync_out_of_office_calendar_events_for_user(google_oauth2_user_pk: int) -> N

@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True)
def sync_out_of_office_calendar_events_for_all_users() -> None:
for google_oauth2_user in GoogleOAuth2User.objects.all():
for google_oauth2_user in GoogleOAuth2User.objects.filter(user__organization__deleted_at__isnull=True):
sync_out_of_office_calendar_events_for_user.apply_async(args=(google_oauth2_user.pk,))
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,21 @@ def _fetch_shift_swap_requests():
ssrs.first().delete()
tasks.sync_out_of_office_calendar_events_for_user(google_oauth2_user_pk)
assert _fetch_shift_swap_requests().count() == 1


@patch("apps.google.tasks.sync_out_of_office_calendar_events_for_user.apply_async")
@pytest.mark.django_db
def test_sync_out_of_office_calendar_events_for_all_users(
mock_sync_out_of_office_calendar_events_for_user,
make_organization_and_user,
make_google_oauth2_user_for_user,
):
organization, user = make_organization_and_user()
google_oauth2_user = make_google_oauth2_user_for_user(user)

deleted_organization, deleted_user = make_organization_and_user()
make_google_oauth2_user_for_user(deleted_user)
deleted_organization.delete()

tasks.sync_out_of_office_calendar_events_for_all_users()
mock_sync_out_of_office_calendar_events_for_user.assert_called_once_with(args=(google_oauth2_user.pk,))
Loading

0 comments on commit 22c644e

Please sign in to comment.