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<{ )}
- {item.expandedView} + {item.expandedView?.()}
- {item.collapsedView} + {item.collapsedView?.(onClick)}
); diff --git a/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx b/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx index cda84f2f..0e283d44 100644 --- a/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx +++ b/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx @@ -1,6 +1,7 @@ import React from 'react'; import cn from 'classnames/bind'; +import { noop } from 'lodash-es'; import Block from 'components/GBlock/Block'; @@ -13,13 +14,20 @@ interface IntegrationBlockProps { hasCollapsedBorder: boolean; heading: React.ReactNode; content: React.ReactNode; + toggle?: () => void; } -const IntegrationBlock: React.FC = ({ heading, content, hasCollapsedBorder, className }) => { +const IntegrationBlock: React.FC = ({ + heading, + content, + hasCollapsedBorder, + className, + toggle = noop, +}) => { return (
{heading && ( - + {heading} )} @@ -28,6 +36,7 @@ const IntegrationBlock: React.FC = ({ heading, content, h className={cx('integrationBlock__content', { 'integrationBlock__content--collapsedBorder': hasCollapsedBorder, })} + onClick={toggle} > {content}
diff --git a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx index 1909db48..4bb4a088 100644 --- a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx +++ b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx @@ -292,6 +292,7 @@ class AlertRules extends React.Component { )} + {alertReceiveChannel.description && (
void; } const CollapsedIntegrationRouteDisplay: React.FC = observer( - ({ channelFilterId, alertReceiveChannelId, routeIndex }) => { + ({ channelFilterId, alertReceiveChannelId, routeIndex, toggle }) => { const { escalationChainStore, alertReceiveChannelStore } = useStore(); const [routeIdForDeletion, setRouteIdForDeletion] = useState(undefined); @@ -44,6 +45,7 @@ const CollapsedIntegrationRouteDisplay: React.FC
@@ -53,7 +55,10 @@ const CollapsedIntegrationRouteDisplay: React.FC {routeWording === 'Default' && ( @@ -91,7 +96,7 @@ const CollapsedIntegrationRouteDisplay: React.FC - Escalate to + Trigger escalation chain: {escalationChain?.name && (
- - No Escalation chain - + No Escalation chain selected )} diff --git a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx index cc6437bd..cf24abe8 100644 --- a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx @@ -32,7 +32,6 @@ import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; import { EscalationChain } from 'models/escalation_chain/escalation_chain.types'; import { MONACO_INPUT_HEIGHT_SMALL, MONACO_OPTIONS } from 'pages/integration_2/Integration2.config'; import IntegrationHelper from 'pages/integration_2/Integration2.helper'; -import { AppFeature } from 'state/features'; import { useStore } from 'state/useStore'; import { UserActions } from 'utils/authorization'; @@ -58,7 +57,6 @@ const ExpandedIntegrationRouteDisplay: React.FC 0; const [{ isEscalationCollapsed, isRefreshingEscalationChains, routeIdForDeletion }, setState] = useReducer( (state: ExpandedIntegrationRouteDisplayState, newState: Partial) => ({ @@ -118,7 +113,7 @@ const ExpandedIntegrationRouteDisplay: React.FC @@ -134,6 +129,16 @@ const ExpandedIntegrationRouteDisplay: React.FC + {routeIndex !== channelFiltersTotal.length - 1 && ( + + + + If the Routing Template is True, group the alerts using the Grouping Template, publish them to + messengers, and trigger the escalation chain. + + + + )} {/* Show Routing Template only for If/Else Routes, not for Default */} {!isDefault && ( @@ -170,14 +175,12 @@ const ExpandedIntegrationRouteDisplay: React.FC )} - {(isSlackInstalled || isTelegramInstalled) && ( - - - Publish to ChatOps - - - - )} + + + Publish to ChatOps + + + @@ -213,9 +216,13 @@ const ExpandedIntegrationRouteDisplay: React.FC - -
diff --git a/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.tsx b/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.tsx index 943dca59..0bb60fd9 100644 --- a/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.tsx +++ b/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.tsx @@ -215,7 +215,7 @@ const MobileAppConnection = observer(({ userPk }: Props) => { onClick={() => onSendTestNotification(true)} disabled={isAttemptingTestNotification} > - Send Critical Test Push notification + Send Important Test Push notification diff --git a/grafana-plugin/src/containers/TemplatePreview/TemplatePreview.module.css b/grafana-plugin/src/containers/TemplatePreview/TemplatePreview.module.css index 5f2ecb4a..41af313f 100644 --- a/grafana-plugin/src/containers/TemplatePreview/TemplatePreview.module.css +++ b/grafana-plugin/src/containers/TemplatePreview/TemplatePreview.module.css @@ -13,3 +13,8 @@ .message code { white-space: break-spaces; } + +.image-result img { + max-width: 100%; + max-height: 100%; +} diff --git a/grafana-plugin/src/containers/TemplatePreview/TemplatePreview.tsx b/grafana-plugin/src/containers/TemplatePreview/TemplatePreview.tsx index f2172173..e268ef25 100644 --- a/grafana-plugin/src/containers/TemplatePreview/TemplatePreview.tsx +++ b/grafana-plugin/src/containers/TemplatePreview/TemplatePreview.tsx @@ -60,7 +60,6 @@ const TemplatePreview = observer((props: TemplatePreviewProps) => { }, 1000); useEffect(handleTemplateBodyChange, [templateBody, payload]); - // onResult(result); return result ? ( <> @@ -93,12 +92,20 @@ const TemplatePreview = observer((props: TemplatePreviewProps) => { )} ) : ( -
+ <> + {templateName.includes('image') ? ( +
+ +
+ ) : ( +
') || ''), + }} + /> + )} + )} ) : ( diff --git a/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.module.css b/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.module.css index 29df3d90..e66b1ba4 100644 --- a/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.module.css +++ b/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.module.css @@ -27,6 +27,15 @@ padding-left: 0; } +.alert-groups-list-item { + cursor: pointer; + margin-bottom: 8px; +} + +.alert-groups-list-item:hover { + background-color: var(--background-secondary); +} + .alert-groups-editor { width: 100%; } diff --git a/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.tsx b/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.tsx index 26cc233b..b58d1988 100644 --- a/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.tsx +++ b/grafana-plugin/src/containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList.tsx @@ -37,6 +37,12 @@ const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) => { .then((result) => setAlertGroupsList(result.slice(0, 30))); }, []); + const getCodeEditorHeight = () => { + const mainDiv = document.getElementById('content-container-id'); + const height = mainDiv?.getBoundingClientRect().height - 59; + return `${height}px`; + }; + const getChangeHandler = () => { return debounce((value: string) => { onEditPayload(value); @@ -65,7 +71,7 @@ const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) => { }; return ( -
+
{selectedAlertPayload ? ( <> {isEditMode ? ( @@ -83,7 +89,7 @@ const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) => { { value={JSON.stringify(selectedAlertPayload, null, 4)} data={undefined} disabled - height={'85vh'} + height={getCodeEditorHeight()} onChange={getChangeHandler()} showLineNumbers useAutoCompleteList={false} @@ -147,7 +153,7 @@ const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) => { data={templates} monacoOptions={MONACO_PAYLOAD_OPTIONS} showLineNumbers={false} - height={'85vh'} + height={getCodeEditorHeight()} onChange={getChangeHandler()} />
@@ -175,10 +181,12 @@ const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) => { <> {alertGroupsList.map((alertGroup) => { return ( -
- +
getAlertGroupPayload(alertGroup.pk)} + className={cx('alert-groups-list-item')} + > + {getAlertGroupName(alertGroup)}
); })} diff --git a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts index 8ab3eb5f..59f23648 100644 --- a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts +++ b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts @@ -13,6 +13,7 @@ export interface AlertReceiveChannelOption { value: number; featured: boolean; short_description: string; + featured_tag_name: string; } export interface AlertReceiveChannelCounters { @@ -43,6 +44,7 @@ export interface AlertReceiveChannel { heartbeat: Heartbeat | null; is_available_for_integration_heartbeat: boolean; routes_count: number; + connected_escalations_chains_count: number; allow_delete: boolean; deleted?: boolean; } diff --git a/grafana-plugin/src/pages/integration_2/Integration2.config.ts b/grafana-plugin/src/pages/integration_2/Integration2.config.ts index b800973a..4c4ded1f 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.config.ts +++ b/grafana-plugin/src/pages/integration_2/Integration2.config.ts @@ -1,3 +1,8 @@ +/* + [oncall-private] + Any change to this file needs to be done in the oncall-private also +*/ + import { KeyValuePair } from 'utils'; export const TEXTAREA_ROWS_COUNT = 4; @@ -45,10 +50,6 @@ const TemplateOptions = { TelegramTitle: new KeyValuePair('telegram_title_template', 'Title'), TelegramMessage: new KeyValuePair('telegram_message_template', 'Message'), TelegramImage: new KeyValuePair('telegram_image_url_template', 'Image'), - /*Should it be in Oncallprivate repo? (All MsTeams)*/ - MSTeamsTitle: new KeyValuePair('MSTeams Title', 'Title'), - MSTeamsMessage: new KeyValuePair('MSTeams Message', 'Message'), - MSTeamsImage: new KeyValuePair('MSTeams Image', 'Image'), Email: new KeyValuePair('Email', 'Email'), Slack: new KeyValuePair('Slack', 'Slack'), @@ -105,24 +106,6 @@ export const INTEGRATION_TEMPLATES_LIST = [ }, ], }, - { - label: TemplateOptions.MSTeams.value, - value: TemplateOptions.MSTeams.key, - children: [ - { - label: TemplateOptions.MSTeamsTitle.value, - value: TemplateOptions.MSTeamsTitle.key, - }, - { - label: TemplateOptions.MSTeamsMessage.value, - value: TemplateOptions.MSTeamsMessage.key, - }, - { - label: TemplateOptions.MSTeamsImage.value, - value: TemplateOptions.MSTeamsImage.key, - }, - ], - }, { label: TemplateOptions.Telegram.value, value: TemplateOptions.Telegram.key, diff --git a/grafana-plugin/src/pages/integration_2/Integration2.helper.ts b/grafana-plugin/src/pages/integration_2/Integration2.helper.ts index aefa772f..4f0f430c 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.helper.ts +++ b/grafana-plugin/src/pages/integration_2/Integration2.helper.ts @@ -46,6 +46,15 @@ const IntegrationHelper = { return routeIndex ? 'Else' : 'If'; }, + getRouteConditionTooltipWording(channelFilters: Array, routeIndex: number) { + const totalCount = Object.keys(channelFilters).length; + + if (routeIndex === totalCount - 1) { + return 'If the alert payload does not match to the previous routes, it will be directed to this default route.'; + } + return 'If the alert payload evaluates the route template as True, it will be directed to this route. It will not be evaluated against the subsequent routes.'; + }, + getMaintenanceText(maintenanceUntill: number, mode: number = undefined) { const date = dayjs(new Date(maintenanceUntill * 1000)); const now = dayjs(); diff --git a/grafana-plugin/src/pages/integration_2/Integration2.module.scss b/grafana-plugin/src/pages/integration_2/Integration2.module.scss index fa155d4e..6fa3f700 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.module.scss +++ b/grafana-plugin/src/pages/integration_2/Integration2.module.scss @@ -104,7 +104,14 @@ $LARGE-MARGIN: 24px; .loadingPlaceholder { margin-bottom: 0; margin-right: 4px; - animation: none; +} + +.integration__description-alert { + padding-top: 24px; + + a { + color: var(--primary-text-link); + } } .customise-button button { diff --git a/grafana-plugin/src/pages/integration_2/Integration2.tsx b/grafana-plugin/src/pages/integration_2/Integration2.tsx index cc5bf960..fd495170 100644 --- a/grafana-plugin/src/pages/integration_2/Integration2.tsx +++ b/grafana-plugin/src/pages/integration_2/Integration2.tsx @@ -12,9 +12,10 @@ import { IconButton, ConfirmModal, Drawer, + Alert, } from '@grafana/ui'; import cn from 'classnames/bind'; -import { get } from 'lodash-es'; +import { get, noop } from 'lodash-es'; import { observer } from 'mobx-react'; import CopyToClipboard from 'react-copy-to-clipboard'; import Emoji from 'react-emoji-render'; @@ -64,7 +65,8 @@ import { openNotification, openErrorNotification } from 'utils'; import { getVar } from 'utils/DOM'; import LocationHelper from 'utils/LocationHelper'; import { UserActions } from 'utils/authorization'; -import { DATASOURCE_ALERTING, PLUGIN_ROOT } from 'utils/consts'; +import { DATASOURCE_GRAFANA, PLUGIN_ROOT } from 'utils/consts'; +import sanitize from 'utils/sanitize'; const cx = cn.bind(styles); @@ -205,10 +207,20 @@ class Integration2 extends React.Component
+ + {alertReceiveChannel.description && ( +
+
} + severity="info" + /> +
+ )}
customIcon: 'plug', canHoverIcon: false, collapsedView: null, - expandedView: , + expandedView: () => , }, { customIcon: 'layer-group', isExpanded: false, isCollapsible: false, canHoverIcon: false, - expandedView: ( + expandedView: () => (
-
+
this.setState({ isTemplateSettingsOpen: true })} + > Grouping: @@ -251,7 +266,10 @@ class Integration2 extends React.Component
-
+
this.setState({ isTemplateSettingsOpen: true })} + > Autoresolve: @@ -260,7 +278,10 @@ class Integration2 extends React.Component
-
+
this.setState({ isTemplateSettingsOpen: true })} + > Visualisation: @@ -290,7 +311,7 @@ class Integration2 extends React.Component isCollapsible: false, collapsedView: null, canHoverIcon: false, - expandedView: ( + expandedView: () => (
@@ -420,14 +441,15 @@ class Integration2 extends React.Component this.setState((prevState) => ({ newRoutes: prevState.newRoutes.filter((r) => r !== channelFilterId) })); } }, - collapsedView: ( + collapsedView: (toggle) => ( ), - expandedView: ( + expandedView: () => ( = ({ id const { alertReceiveChannelStore } = useStore(); const alertReceiveChannelCounter = alertReceiveChannelStore.counters[id]; const alertReceiveChannel = alertReceiveChannelStore.items[id]; - const isAlertManager = alertReceiveChannel.integration === DATASOURCE_ALERTING; + const isGrafanaDatasource = alertReceiveChannel.integration === DATASOURCE_GRAFANA; const hasAlerts = !!alertReceiveChannelCounter?.alerts_count; return ( = ({ id
} - content={isAlertManager || !hasAlerts ? renderContent() : null} + content={isGrafanaDatasource || !hasAlerts ? renderContent() : null} /> ); @@ -930,14 +953,14 @@ const HowToConnectComponent: React.FC<{ id: AlertReceiveChannel['id'] }> = ({ id )} - {isAlertManager && ( + {isGrafanaDatasource && ( - + Contact Point and - + Notification Policy created in Grafana Alerting @@ -953,14 +976,12 @@ interface IntegrationHeaderProps { alertReceiveChannelCounter: AlertReceiveChannelCounters; alertReceiveChannel: AlertReceiveChannel; integration: SelectOption; - channelFilterIds: string[]; } const IntegrationHeader: React.FC = ({ integration, alertReceiveChannelCounter, alertReceiveChannel, - channelFilterIds, }) => { const { grafanaTeamStore, heartbeatStore, alertReceiveChannelStore } = useStore(); @@ -984,9 +1005,17 @@ const IntegrationHeader: React.FC = ({ {alertReceiveChannel.maintenance_till && ( @@ -1044,7 +1073,7 @@ const IntegrationHeader: React.FC = ({ if ( !alertReceiveChannel.is_available_for_integration_heartbeat || - alertReceiveChannel.heartbeat?.last_heartbeat_time_verbal === null + !alertReceiveChannel.heartbeat?.last_heartbeat_time_verbal ) { return null; } diff --git a/grafana-plugin/src/pages/integrations_2/Integrations2.tsx b/grafana-plugin/src/pages/integrations_2/Integrations2.tsx index ded78648..4750cf75 100644 --- a/grafana-plugin/src/pages/integrations_2/Integrations2.tsx +++ b/grafana-plugin/src/pages/integrations_2/Integrations2.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { HorizontalGroup, Button, IconButton } from '@grafana/ui'; +import { HorizontalGroup, Button, IconButton, VerticalGroup } from '@grafana/ui'; import cn from 'classnames/bind'; import { debounce } from 'lodash-es'; import { observer } from 'mobx-react'; @@ -165,7 +165,12 @@ class Integrations extends React.Component
- Integrations 2 + + Integrations 2 + + Receive alerts, group and interpret using templates and route to escalations + +
- +