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.