diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09bf18d2..d79034ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## v1.2.34 (2023-05-31)
+
+### Added
+
+- Add description to "Default channel for Slack notifications" UI dropdown by @joeyorlando ([2051](https://github.com/grafana/oncall/pull/2051))
+
+### Fixed
+
+- Fix templates when slack or telegram is disabled ([#2064](https://github.com/grafana/oncall/pull/2064))
+- Reduce number of alert groups returned by `Attach To` in slack to avoid event trigger timeout @mderynck ([#2049](https://github.com/grafana/oncall/pull/2049))
+
## v1.2.33 (2023-05-30)
### Fixed
diff --git a/docs/sources/mobile-app/on-call-status-and-shifts/index.md b/docs/sources/mobile-app/on-call-status-and-shifts/index.md
index 2736c9cf..359f5f2f 100644
--- a/docs/sources/mobile-app/on-call-status-and-shifts/index.md
+++ b/docs/sources/mobile-app/on-call-status-and-shifts/index.md
@@ -13,6 +13,9 @@ weight: 500
# On-call status and shifts
On the **Feed** page, your avatar on top of the screen indicates whether you are oncall, will be on call soon, or not.
-Tap on it to open the **upcoming shifts** view. This view presents your current, and next upcoming shifts (if any), up to 1 month into the future.
+Tap it to open the **upcoming shifts** view. This view displays:
-
+- your current shift (if any), per schedule.
+- your next upcoming shift (if any), per schedule. This only looks up to 31 days into the future.
+
+
diff --git a/docs/sources/mobile-app/push-notifications/index.md b/docs/sources/mobile-app/push-notifications/index.md
index efdddd78..e5f569f9 100644
--- a/docs/sources/mobile-app/push-notifications/index.md
+++ b/docs/sources/mobile-app/push-notifications/index.md
@@ -25,7 +25,7 @@ To receive push notifications from the Grafana OnCall mobile app, you must add t
In the **Settings** tab of the mobile app, tap on **Notification policies** to review, reorder, remove, add or change steps.
Alternatively, you can do the same on desktop. From Grafana OnCall, navigate to the **Users** page, click **View my profile** and navigate to the **User Info** tab.
-
+
## Configuration
@@ -33,20 +33,31 @@ Use the **Push notifications** section in the **Settings** tab to configure push
You can always confirm how a notification is presented by going to Grafana OnCall on your desktop,
navigate to the **Users** page, click **View my profile** and navigate to the **Mobile App connection** tab.
-Here you can send a test notification of default or important priority.
+Here you can send a test notification of default or important priority. We recommend doing this to try out
+correct configuration of **Do Not Disturb** and **Volume** overrides.
### Android
On Android, we leverage the "Notification channels" system feature.
Each type of notification (**important**, **default**, and **on-call shifts**) registers a channel.
-In this channel, you may configure the sound style, optional Do Not Disturb override, vibration, and so on.
+In this channel, you may configure the sound style, vibration, and so on.
**Customize notifications** takes you to this system menu, while hitting the **back** button or swiping left (if enabled) takes you back to the application.
+>**Note**: You can explore included sounds and recommendations via the **Sound Library** button, but to change the sound, go to **Customize notifications**.
+
+#### Override Do Not Disturb
+
+- On most Android versions, the **Override Do Not Disturb** option is available in the channel options described above.
+- On some Samsung devices, you can add the Grafana Oncall app under (System) Settings > Notifications > Do not disturb > App notifications.
+- If your device does something different, you may need to search for this setting for notifications via the (System) Settings app.
+ Do not confuse this with the **Override Do Not Disturb** application permission, needed for **Volume Overrides** (see below).
+
+#### Override Volume
+
**Volume Override** can optionally be configured in the mobile app itself.
Confusingly, this requires you to provide the **Override Do Not Disturb** permission to the application, in the system configuration.
-The app will prompt for this if applicable.
-
->**Note**: You can explore included sounds and recommendations via the **Sound Library** button, but to change the sound, go to **Customize notifications**.
+The app will prompt for this if applicable. Note that this is a different setting than the **Do Not Disturb** override needed for
+notifications triggered by the application, which is described above.
diff --git a/engine/apps/alerts/integration_options_mixin.py b/engine/apps/alerts/integration_options_mixin.py
index a747d899..0594e0f6 100644
--- a/engine/apps/alerts/integration_options_mixin.py
+++ b/engine/apps/alerts/integration_options_mixin.py
@@ -49,6 +49,11 @@ class IntegrationOptionsMixin:
integration_config.slug: integration_config.short_description for integration_config in _config
}
INTEGRATION_FEATURED = [integration_config.slug for integration_config in _config if integration_config.is_featured]
+ INTEGRATION_FEATURED_TAG_NAME = {
+ integration_config.slug: integration_config.featured_tag_name
+ for integration_config in _config
+ if hasattr(integration_config, "featured_tag_name")
+ }
# The following attributes dynamically generated and used by apps.alerts.incident_appearance.renderers, templaters
# e.g. INTEGRATION_TO_DEFAULT_SLACK_TITLE_TEMPLATE, INTEGRATION_TO_DEFAULT_SLACK_MESSAGE_TEMPLATE, etc...
diff --git a/engine/apps/alerts/tasks/notify_ical_schedule_shift.py b/engine/apps/alerts/tasks/notify_ical_schedule_shift.py
index c8bfec12..93e11209 100644
--- a/engine/apps/alerts/tasks/notify_ical_schedule_shift.py
+++ b/engine/apps/alerts/tasks/notify_ical_schedule_shift.py
@@ -255,6 +255,7 @@ def notify_ical_schedule_shift(schedule_pk):
for prev_ical_file, current_ical_file in prev_and_current_ical_files:
if prev_ical_file and (not current_ical_file or not is_icals_equal(current_ical_file, prev_ical_file)):
+ task_logger.info(f"ical files are different")
# If icals are not equal then compare current_events from them
is_prev_ical_diff = True
prev_calendar = icalendar.Calendar.from_ical(prev_ical_file)
@@ -295,6 +296,7 @@ def notify_ical_schedule_shift(schedule_pk):
shift_changed, diff_uids = calculate_shift_diff(current_shifts, prev_shifts)
if shift_changed:
+ task_logger.info(f"shifts_changed: {diff_uids}")
# Get only new/changed shifts to send a reminder message.
new_shifts = []
for uid in diff_uids:
@@ -361,6 +363,7 @@ def notify_ical_schedule_shift(schedule_pk):
schedule.save(update_fields=["current_shifts", "empty_oncall"])
if len(new_shifts) > 0 or empty_oncall:
+ task_logger.info(f"new_shifts: {new_shifts}")
slack_client = SlackClientWithErrorHandling(schedule.organization.slack_team_identity.bot_access_token)
step = scenario_step.ScenarioStep.get_step("schedules", "EditScheduleShiftNotifyStep")
report_blocks = step.get_report_blocks_ical(new_shifts, upcoming_shifts, schedule, empty_oncall)
diff --git a/engine/apps/api/serializers/alert_receive_channel.py b/engine/apps/api/serializers/alert_receive_channel.py
index d3d0d91d..7d070d4f 100644
--- a/engine/apps/api/serializers/alert_receive_channel.py
+++ b/engine/apps/api/serializers/alert_receive_channel.py
@@ -216,23 +216,6 @@ class FilterAlertReceiveChannelSerializer(serializers.ModelSerializer):
class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.ModelSerializer):
id = serializers.CharField(read_only=True, source="public_primary_key")
- CORE_TEMPLATE_NAMES = [
- "slack_title_template",
- "slack_message_template",
- "slack_image_url_template",
- "web_title_template",
- "web_message_template",
- "web_image_url_template",
- "telegram_title_template",
- "telegram_message_template",
- "telegram_image_url_template",
- "sms_title_template",
- "phone_call_title_template",
- "source_link_template",
- "grouping_id_template",
- "resolve_condition_template",
- "acknowledge_condition_template",
- ]
payload_example = SerializerMethodField()
is_based_on_alertmanager = SerializerMethodField()
@@ -325,9 +308,7 @@ class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.Mode
"""Update core templates if needed."""
errors = {}
- core_template_names = self.CORE_TEMPLATE_NAMES
-
- for field_name in core_template_names:
+ for field_name in self.core_templates_names:
value = data.get(field_name)
validator = jinja_template_env.from_string
if value is not None:
@@ -343,7 +324,6 @@ class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.Mode
def to_representation(self, obj):
ret = super().to_representation(obj)
- ret = self._get_templates_to_show(ret)
core_templates = self._get_core_templates(obj)
ret.update(core_templates)
@@ -354,29 +334,6 @@ class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.Mode
return ret
- def _get_templates_to_show(self, response_data):
- """
- For On-prem installations with disabled features it is needed to disable corresponding templates
- """
- slack_integration_required_templates = [
- "slack_title_template",
- "slack_message_template",
- "slack_image_url_template",
- ]
- telegram_integration_required_templates = [
- "telegram_title_template",
- "telegram_message_template",
- "telegram_image_url_template",
- ]
- if not settings.FEATURE_SLACK_INTEGRATION_ENABLED:
- for st in slack_integration_required_templates:
- response_data.pop(st)
- if not settings.FEATURE_TELEGRAM_INTEGRATION_ENABLED:
- for tt in telegram_integration_required_templates:
- response_data.pop(tt)
-
- return response_data
-
def _get_messaging_backend_templates(self, obj):
"""Return additional messaging backend templates if any."""
templates = {}
@@ -399,8 +356,7 @@ class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.Mode
def _get_core_templates(self, obj):
core_templates = {}
- core_template_names = self.CORE_TEMPLATE_NAMES
- for template_name in core_template_names:
+ for template_name in self.core_templates_names:
template_value = getattr(obj, template_name)
defaults = getattr(obj, f"INTEGRATION_TO_DEFAULT_{template_name.upper()}", {})
default_template_value = defaults.get(obj.integration)
@@ -408,3 +364,40 @@ class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.Mode
core_templates[f"{template_name}_is_default"] = not bool(template_value)
return core_templates
+
+ @property
+ def core_templates_names(self):
+ """
+ core_templates_names returns names of templates introduced before messaging backends system with respect to
+ enabled integrations.
+ """
+ core_templates = [
+ "web_title_template",
+ "web_message_template",
+ "web_image_url_template",
+ "sms_title_template",
+ "phone_call_title_template",
+ "source_link_template",
+ "grouping_id_template",
+ "resolve_condition_template",
+ "acknowledge_condition_template",
+ ]
+
+ slack_integration_required_templates = [
+ "slack_title_template",
+ "slack_message_template",
+ "slack_image_url_template",
+ ]
+ telegram_integration_required_templates = [
+ "telegram_title_template",
+ "telegram_message_template",
+ "telegram_image_url_template",
+ ]
+
+ apppend = []
+
+ if settings.FEATURE_SLACK_INTEGRATION_ENABLED:
+ core_templates += slack_integration_required_templates
+ if settings.FEATURE_TELEGRAM_INTEGRATION_ENABLED:
+ core_templates += telegram_integration_required_templates
+ return apppend + core_templates
diff --git a/engine/apps/api/tests/test_alert_receive_channel_template.py b/engine/apps/api/tests/test_alert_receive_channel_template.py
index 4c134855..728add84 100644
--- a/engine/apps/api/tests/test_alert_receive_channel_template.py
+++ b/engine/apps/api/tests/test_alert_receive_channel_template.py
@@ -1,6 +1,7 @@
from unittest.mock import patch
import pytest
+from django.test.utils import override_settings
from django.urls import reverse
from rest_framework import status
from rest_framework.response import Response
@@ -381,3 +382,44 @@ def test_update_alert_receive_channel_templates(
assert updated_templates_data[template_name] is False
else:
assert updated_templates_data[template_name] == template_update_func(prev_template_value)
+
+
+@override_settings(FEATURE_TELEGRAM_INTEGRATION_ENABLED=False)
+@override_settings(FEATURE_SLACK_INTEGRATION_ENABLED=False)
+@pytest.mark.django_db
+def test_update_alert_receive_channel_backend_template_hide_disabled_integration_templates(
+ make_organization_and_user_with_plugin_token,
+ make_user_auth_headers,
+ make_alert_receive_channel,
+):
+ slack_integration_required_templates = [
+ "slack_title_template",
+ "slack_message_template",
+ "slack_image_url_template",
+ ]
+ telegram_integration_required_templates = [
+ "telegram_title_template",
+ "telegram_message_template",
+ "telegram_image_url_template",
+ ]
+
+ organization, user, token = make_organization_and_user_with_plugin_token()
+ alert_receive_channel = make_alert_receive_channel(
+ organization,
+ messaging_backends_templates={"TESTONLY": {"title": "the-title", "message": "the-message", "image_url": "url"}},
+ )
+ client = APIClient()
+
+ url = reverse(
+ "api-internal:alert_receive_channel_template-detail", kwargs={"pk": alert_receive_channel.public_primary_key}
+ )
+
+ response = client.get(url, format="json", **make_user_auth_headers(user, token))
+ assert response.status_code == status.HTTP_200_OK
+ templates_data = response.json()
+
+ for st in slack_integration_required_templates:
+ assert st not in templates_data
+
+ for tt in telegram_integration_required_templates:
+ assert tt not in templates_data
diff --git a/engine/apps/api/views/alert_receive_channel.py b/engine/apps/api/views/alert_receive_channel.py
index 95cb3120..cd4e2552 100644
--- a/engine/apps/api/views/alert_receive_channel.py
+++ b/engine/apps/api/views/alert_receive_channel.py
@@ -198,6 +198,9 @@ class AlertReceiveChannelView(
"display_name": integration_title,
"short_description": AlertReceiveChannel.INTEGRATION_SHORT_DESCRIPTION[integration_id],
"featured": integration_id in AlertReceiveChannel.INTEGRATION_FEATURED,
+ "featured_tag_name": AlertReceiveChannel.INTEGRATION_FEATURED_TAG_NAME[integration_id]
+ if integration_id in AlertReceiveChannel.INTEGRATION_FEATURED_TAG_NAME
+ else None,
}
# if integration is featured we show it in the beginning
if choice["featured"]:
diff --git a/engine/apps/email/migrations/0002_alter_emailmessage_receiver.py b/engine/apps/email/migrations/0002_alter_emailmessage_receiver.py
new file mode 100644
index 00000000..7a2b29d7
--- /dev/null
+++ b/engine/apps/email/migrations/0002_alter_emailmessage_receiver.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.19 on 2023-05-30 16:45
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('user_management', '0011_auto_20230411_1358'),
+ ('email', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='emailmessage',
+ name='receiver',
+ field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='user_management.user'),
+ ),
+ ]
diff --git a/engine/apps/email/models.py b/engine/apps/email/models.py
index 0db26986..3c749297 100644
--- a/engine/apps/email/models.py
+++ b/engine/apps/email/models.py
@@ -16,5 +16,5 @@ class EmailMessage(models.Model):
"base.UserNotificationPolicy", on_delete=models.SET_NULL, null=True, default=None
)
- receiver = models.ForeignKey("user_management.User", on_delete=models.PROTECT, null=True, default=None)
+ receiver = models.ForeignKey("user_management.User", on_delete=models.CASCADE, null=True, default=None)
created_at = models.DateTimeField(auto_now_add=True)
diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py
index a916255d..7b67a991 100644
--- a/engine/apps/slack/scenarios/distribute_alerts.py
+++ b/engine/apps/slack/scenarios/distribute_alerts.py
@@ -37,6 +37,8 @@ from common.utils import clean_markup, is_string_with_visible_characters
from .step_mixins import CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin
+ATTACH_TO_ALERT_GROUPS_LIMIT = 20
+
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
@@ -398,7 +400,7 @@ class SelectAttachGroupStep(
.order_by("-pk")
)
- for alert_group_to_attach in alert_groups_queryset[:60]:
+ for alert_group_to_attach in alert_groups_queryset[:ATTACH_TO_ALERT_GROUPS_LIMIT]:
# long_verbose_name_without_formatting was removed from here because it increases queries count due to
# alerts.first().
# alert_group_to_attach.alerts.exists() and alerts.all()[0] don't make additional queries to db due to
@@ -435,7 +437,7 @@ class SelectAttachGroupStep(
"text": "Attach to...",
},
"action_id": AttachGroupStep.routing_uid(),
- "options": collected_options[:60],
+ "options": collected_options[:ATTACH_TO_ALERT_GROUPS_LIMIT],
},
"label": {
"type": "plain_text",
diff --git a/engine/config_integrations/grafana_alerting.py b/engine/config_integrations/grafana_alerting.py
index 0b72ff20..5b0deb0c 100644
--- a/engine/config_integrations/grafana_alerting.py
+++ b/engine/config_integrations/grafana_alerting.py
@@ -8,6 +8,7 @@ short_description = (
description = None
is_displayed_on_web = True
is_featured = True
+featured_tag_name = "Quick Connect"
is_able_to_autoresolve = True
is_demo_alert_enabled = True
diff --git a/engine/config_integrations/webhook.py b/engine/config_integrations/webhook.py
index 823bc837..0041c8c1 100644
--- a/engine/config_integrations/webhook.py
+++ b/engine/config_integrations/webhook.py
@@ -2,9 +2,10 @@
enabled = True
title = "Webhook"
slug = "webhook"
-short_description = None
+short_description = "If your monitoring system isn't listed, choose Webhook for generic templates, and feel free to modify them as needed."
description = None
-is_featured = False
+is_featured = True
+featured_tag_name = "Generic"
is_displayed_on_web = True
is_able_to_autoresolve = True
is_demo_alert_enabled = True
diff --git a/grafana-plugin/src/components/CheatSheet/CheatSheet.module.css b/grafana-plugin/src/components/CheatSheet/CheatSheet.module.css
index 2915452c..db596036 100644
--- a/grafana-plugin/src/components/CheatSheet/CheatSheet.module.css
+++ b/grafana-plugin/src/components/CheatSheet/CheatSheet.module.css
@@ -6,6 +6,12 @@
min-width: min-content;
}
+.cheatsheet-container > div {
+ height: 100%;
+ max-height: 100%;
+ overflow-y: scroll;
+}
+
.cheatsheet-item {
margin-bottom: 24px;
}
diff --git a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx
index 4c212a8b..a57708d0 100644
--- a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx
+++ b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx
@@ -11,8 +11,8 @@ const cx = cn.bind(styles);
export interface IntegrationCollapsibleItem {
customIcon?: IconName;
canHoverIcon: boolean;
- expandedView: React.ReactNode;
- collapsedView: React.ReactNode;
+ collapsedView: (toggle?: () => void) => React.ReactNode; // needs toggle param for toggling on click
+ expandedView: () => React.ReactNode; // for consistency, this is also a function
isCollapsible: boolean;
isExpanded?: boolean;
onStateChange?(): void;
@@ -116,10 +116,10 @@ const IntegrationCollapsibleTreeItem: React.FC<{
)}