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:
parent
07e3a78949
commit
4967ce8208
7 changed files with 68 additions and 63 deletions
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue