diff --git a/CHANGELOG.md b/CHANGELOG.md index 90523c1c..3e84b77a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add 2, 3 and 6 hours silence options +- Add 2, 3 and 6 hours Alert Group silence options by @tommysitehost ([#1822](https://github.com/grafana/oncall/pull/1822)) - Add schedule related users endpoint to plugin API -## Fixed +### Changed + +- Update web UI, Slack, and Telegram to allow silencing an acknowledged alert group by @joeyorlando ([#1831](https://github.com/grafana/oncall/pull/1831)) + +### Fixed - Optimize duplicate queries occurring in AlertGroupFilter by @joeyorlando ([1809](https://github.com/grafana/oncall/pull/1809)) diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index b5e068a3..8c6987ab 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -74,7 +74,7 @@ exclusive states: - **Firing:** Once Alert Group is registered, Escalation Policy associated with it is getting started. Escalation policy will work while Alert Group is in this status. - **Acknowledged:** Ongoing Escalation Chain will be interrupted. Unacknowledge will move Alert Group to the "Firing" state and will re-launch Escalation Chain. - **Silenced:** Similar to "Acknowledged" but designed to be temporary with a timeout. Once time is out, will re-launch Escalation Chain and move Alert Group -to the "Firing" state. + to the "Firing" state. - **Resolved:** Similar to "Acknowledged". Possible transitions: @@ -85,6 +85,7 @@ Possible transitions: - Silenced -> Firing - Silenced -> Acknowledged - Silenced -> Resolved +- Acknowledged -> Silenced - Acknowledged -> Firing - Acknowledged -> Resolved - Resolved -> Firing diff --git a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py index 708effaf..0718bd68 100644 --- a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py @@ -218,34 +218,31 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer): text = "Invite..." invitation_element = self._get_select_user_element(action_id, text=text) buttons.append(invitation_element) - if not self.alert_group.acknowledged: - if not self.alert_group.silenced: - silence_options = [ - {"text": {"type": "plain_text", "text": text, "emoji": True}, "value": str(value)} - for value, text in AlertGroup.SILENCE_DELAY_OPTIONS - ] - buttons.append( - { - "placeholder": {"type": "plain_text", "text": "Silence", "emoji": True}, - "type": "static_select", - "options": silence_options, - "action_id": ScenarioStep.get_step( - "distribute_alerts", "SilenceGroupStep" - ).routing_uid(), - # "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), - } - ) - else: - buttons.append( - { - "text": {"type": "plain_text", "text": "Unsilence", "emoji": True}, - "type": "button", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), - "action_id": ScenarioStep.get_step( - "distribute_alerts", "UnSilenceGroupStep" - ).routing_uid(), - }, - ) + + if not self.alert_group.silenced: + silence_options = [ + {"text": {"type": "plain_text", "text": text, "emoji": True}, "value": str(value)} + for value, text in AlertGroup.SILENCE_DELAY_OPTIONS + ] + buttons.append( + { + "placeholder": {"type": "plain_text", "text": "Silence", "emoji": True}, + "type": "static_select", + "options": silence_options, + "action_id": ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep").routing_uid(), + # "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + } + ) + else: + buttons.append( + { + "text": {"type": "plain_text", "text": "Unsilence", "emoji": True}, + "type": "button", + "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "action_id": ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep").routing_uid(), + }, + ) + attach_button = { "text": {"type": "plain_text", "text": "Attach to ...", "emoji": True}, "type": "button", diff --git a/engine/apps/telegram/renderers/keyboard.py b/engine/apps/telegram/renderers/keyboard.py index 71da3263..c7573181 100644 --- a/engine/apps/telegram/renderers/keyboard.py +++ b/engine/apps/telegram/renderers/keyboard.py @@ -55,7 +55,7 @@ class TelegramKeyboardRenderer: rows.append([self.resolve_button]) # Silence/Unsilence buttons - if not self.alert_group.acknowledged and not self.alert_group.resolved: + if not self.alert_group.resolved: if not self.alert_group.silenced: rows.append(self.silence_buttons) else: diff --git a/engine/apps/telegram/tests/test_keyboard_renderer.py b/engine/apps/telegram/tests/test_keyboard_renderer.py index 9628c185..449b844b 100644 --- a/engine/apps/telegram/tests/test_keyboard_renderer.py +++ b/engine/apps/telegram/tests/test_keyboard_renderer.py @@ -32,6 +32,23 @@ def are_keyboards_equal(keyboard: List[List[InlineKeyboardButton]], other: List[ return True +def generate_silence_buttons(alert_group, organization) -> List: + return [ + InlineKeyboardButton( + text="🔕 forever", + callback_data=f"{alert_group.pk}:4:oncall-uuid{organization.uuid}", + ), + InlineKeyboardButton( + text="... for 1h", + callback_data=f"{alert_group.pk}:4:3600:oncall-uuid{organization.uuid}", + ), + InlineKeyboardButton( + text="... for 4h", + callback_data=f"{alert_group.pk}:4:14400:oncall-uuid{organization.uuid}", + ), + ] + + @pytest.mark.django_db def test_actions_keyboard_alerting(make_organization, make_alert_receive_channel, make_alert_group, make_alert): organization = make_organization() @@ -58,20 +75,7 @@ def test_actions_keyboard_alerting(make_organization, make_alert_receive_channel callback_data=f"{alert_group.pk}:2:oncall-uuid{organization.uuid}", ) ], - [ - InlineKeyboardButton( - text="🔕 forever", - callback_data=f"{alert_group.pk}:4:oncall-uuid{organization.uuid}", - ), - InlineKeyboardButton( - text="... for 1h", - callback_data=f"{alert_group.pk}:4:3600:oncall-uuid{organization.uuid}", - ), - InlineKeyboardButton( - text="... for 4h", - callback_data=f"{alert_group.pk}:4:14400:oncall-uuid{organization.uuid}", - ), - ], + generate_silence_buttons(alert_group, organization), ] assert are_keyboards_equal(keyboard.inline_keyboard, expected_keyboard) is True @@ -106,6 +110,7 @@ def test_actions_keyboard_acknowledged( callback_data=f"{alert_group.pk}:2:oncall-uuid{organization.uuid}", ) ], + generate_silence_buttons(alert_group, organization), ] assert are_keyboards_equal(keyboard.inline_keyboard, expected_keyboard) is True diff --git a/grafana-plugin/src/pages/incident/Incident.helpers.tsx b/grafana-plugin/src/pages/incident/Incident.helpers.tsx index 581618ef..f529744d 100644 --- a/grafana-plugin/src/pages/incident/Incident.helpers.tsx +++ b/grafana-plugin/src/pages/incident/Incident.helpers.tsx @@ -189,17 +189,6 @@ export function getActionButtons(incident: AlertType, cx: any, callbacks: { [key const buttons = []; if (incident.alert_receive_channel.integration !== MaintenanceIntegration) { - if (incident.status === IncidentStatus.Firing) { - buttons.push( - - ); - } - if (incident.status === IncidentStatus.Silenced) { buttons.push( @@ -208,6 +197,15 @@ export function getActionButtons(incident: AlertType, cx: any, callbacks: { [key ); + } else if (incident.status !== IncidentStatus.Resolved) { + buttons.push( + + ); } if (!incident.resolved && !incident.acknowledged) { diff --git a/grafana-plugin/src/pages/settings/tabs/Cloud/CloudPage.tsx b/grafana-plugin/src/pages/settings/tabs/Cloud/CloudPage.tsx index 7a501420..1fbd7b56 100644 --- a/grafana-plugin/src/pages/settings/tabs/Cloud/CloudPage.tsx +++ b/grafana-plugin/src/pages/settings/tabs/Cloud/CloudPage.tsx @@ -331,7 +331,7 @@ const CloudPage = observer((props: CloudPageProps) => { @@ -351,8 +351,8 @@ const CloudPage = observer((props: CloudPageProps) => { Monitor instance with heartbeat - Once connected, current OnCall instance will send heartbeats every 3 minutes to the cloud Instance. If no - heartbeat will be received in 10 minutes, cloud instance will issue an alert. + Once connected, this OnCall instance will send heartbeats every 3 minutes to the Cloud Grafana instance. If + no heartbeats are received within 10 minutes, the Cloud Grafana instance will issue an alert. @@ -363,7 +363,7 @@ const CloudPage = observer((props: CloudPageProps) => { - Connecting to Cloud OnCall enables sending SMS and phone call notifications using the cloud Grafana OnCall. + Connecting to Cloud OnCall enables sending SMS and phone call notifications using Cloud Grafana OnCall. @@ -373,8 +373,8 @@ const CloudPage = observer((props: CloudPageProps) => { Mobile app push notifications - Connecting to Cloud OnCall enables sending push notifications on mobile devices using the Grafana OnCall - mobile app. + Connecting to Cloud Grafana OnCall enables sending push notifications on mobile devices using the Grafana + OnCall mobile app.