update web UI, Slack, and Telegram to allow silencing an acknowledged alert group (#1831)

# What this PR does

https://www.loom.com/share/1a6ef0d00c3b46ca80c120579d512dcc

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated (N/A)
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
This commit is contained in:
Joey Orlando 2023-04-27 10:52:35 -04:00 committed by GitHub
parent 07e3a78949
commit 4967ce8208
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 68 additions and 63 deletions

View file

@ -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))

View file

@ -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

View file

@ -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",

View file

@ -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:

View file

@ -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

View file

@ -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(
<SilenceButtonCascader
className={cx('silence-button-inline')}
key="silence"
disabled={incident.loading || incident.is_restricted}
onSelect={onSilence}
/>
);
}
if (incident.status === IncidentStatus.Silenced) {
buttons.push(
<WithPermissionControlTooltip key="silence" userAction={UserActions.AlertGroupsWrite}>
@ -208,6 +197,15 @@ export function getActionButtons(incident: AlertType, cx: any, callbacks: { [key
</Button>
</WithPermissionControlTooltip>
);
} else if (incident.status !== IncidentStatus.Resolved) {
buttons.push(
<SilenceButtonCascader
className={cx('silence-button-inline')}
key="silence"
disabled={incident.loading || incident.is_restricted}
onSelect={onSilence}
/>
);
}
if (!incident.resolved && !incident.acknowledged) {

View file

@ -331,7 +331,7 @@ const CloudPage = observer((props: CloudPageProps) => {
</Text.Title>
<Field
label=""
description="Find it in you Cloud OnCall -> Settings page"
description="Find it on the Settings page of OnCall, within your Cloud Grafana instance"
style={{ width: '100%' }}
invalid={apiKeyError}
>
@ -351,8 +351,8 @@ const CloudPage = observer((props: CloudPageProps) => {
Monitor instance with heartbeat
</Text.Title>
<Text type="secondary">
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.
</Text>
</VerticalGroup>
</Block>
@ -363,7 +363,7 @@ const CloudPage = observer((props: CloudPageProps) => {
</Text.Title>
<Text type="secondary">
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.
</Text>
</VerticalGroup>
</Block>
@ -373,8 +373,8 @@ const CloudPage = observer((props: CloudPageProps) => {
<Icon name="mobile-android" className={cx('block-icon')} size="lg" /> Mobile app push notifications
</Text.Title>
<Text type="secondary">
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.
</Text>
</VerticalGroup>
</Block>