mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 13:39:47 +00:00
Explain alert acknowledgement and snooze without implying recovery
Document existing incident holds, hidden active alerts, bulk scope and the local-calendar snooze preset. Link delivery troubleshooting to the controls and verify the rendered help and acknowledgement readback. Contract-Neutral: Existing help and verification only; no alert runtime, API, schema or frontend control changes. Change-source: pulse-maintainer
This commit is contained in:
@@ -549,6 +549,40 @@ This tolerance delays activation; it does not disable powered-off monitoring.
|
||||
Use the existing guest offline-alert toggle when a guest should never produce
|
||||
powered-off alerts.
|
||||
|
||||
### Acknowledge and snooze existing alerts
|
||||
|
||||
In **Alerts → Overview**, these actions apply to an existing incident, not its
|
||||
threshold rule. Monitoring continues; neither action repairs the workload or
|
||||
confirms recovery.
|
||||
|
||||
| Action | Effect |
|
||||
| --- | --- |
|
||||
| **Acknowledge** | Marks the incident as seen. While acknowledged, further firing notifications, escalation and its recovery notification are suppressed. The underlying alert can remain active. |
|
||||
| **Unacknowledge** | Removes that acknowledgement. Normal notification policies apply again; this is not a guaranteed immediate resend. |
|
||||
| **Snooze** | Pauses notifications and escalation for this incident until the selected time, including critical notifications. Monitoring can still detect recovery during the snooze. |
|
||||
| **Resume** | Ends this incident's snooze early. It does not remove an acknowledgement or clear the underlying alert. |
|
||||
|
||||
Acknowledged alerts are hidden from the default active list and excluded from
|
||||
its **Active** count. Use **Show acknowledged** to inspect them and
|
||||
**Unacknowledge** only when they need attention again. **No unacknowledged
|
||||
alerts** does not mean every workload recovered. Before **Acknowledge all**, read
|
||||
the count and scope: the toolbar applies to all unacknowledged active alerts;
|
||||
a group's button applies to that group, including its collapsed related alerts.
|
||||
Use the individual action when only one incident has been reviewed.
|
||||
|
||||
Choose a **Snooze** duration on the incident's card and check its **Snoozed until**
|
||||
time. **Until tomorrow at 9:00** uses the browser's local timezone, not the
|
||||
[quiet-hours timezone](#quiet-hours-and-notification-holds). On expiry or
|
||||
**Resume**, an acknowledged incident remains acknowledged; remove that hold
|
||||
separately if intended. Other schedules, routing, cooldowns and destination
|
||||
settings still apply, so ending a snooze is not proof of delivery or a promise
|
||||
to replay missed notifications.
|
||||
|
||||
These incident actions are not **Dismiss retained failures** or **Retry retained
|
||||
deliveries**. They do not retry a failed notification. Check fresh readings and
|
||||
the workload for recovery, and [Recent delivery activity](TROUBLESHOOTING.md#test-succeeds-but-real-alerts-are-missing)
|
||||
for delivery. Do not create an outage or lower thresholds to test an action.
|
||||
|
||||
### Quiet hours and notification holds
|
||||
|
||||
Open **Alerts → Schedule → Quiet hours** to enable a notification quiet period.
|
||||
|
||||
@@ -623,8 +623,10 @@ alert was generated, routed or delivered to the intended recipient.
|
||||
Turn delivery on there only when you intend to send alerts.
|
||||
- Check the affected alert, the destination's **Enabled** state, minimum alert
|
||||
severity and tag filters. Review the [quiet-hours schedule](CONFIGURATION.md#quiet-hours-and-notification-holds)
|
||||
and any mute, acknowledgement or maintenance policy before treating an absent
|
||||
attempt as a transport failure.
|
||||
and [acknowledgement or snooze](CONFIGURATION.md#acknowledge-and-snooze-existing-alerts),
|
||||
plus any mute or maintenance policy, before treating an absent attempt as a
|
||||
transport failure. In **Alerts → Overview**, use **Show acknowledged** to find
|
||||
hidden incidents; **Resume** ends a snooze but does not remove acknowledgement.
|
||||
- Use **Recent delivery activity** to correlate the original alert, destination
|
||||
and absolute timestamp, including held-notification reasons. An empty window
|
||||
is not proof of healthy delivery; an **unavailable** read is not an empty log.
|
||||
|
||||
@@ -549,6 +549,40 @@ This tolerance delays activation; it does not disable powered-off monitoring.
|
||||
Use the existing guest offline-alert toggle when a guest should never produce
|
||||
powered-off alerts.
|
||||
|
||||
### Acknowledge and snooze existing alerts
|
||||
|
||||
In **Alerts → Overview**, these actions apply to an existing incident, not its
|
||||
threshold rule. Monitoring continues; neither action repairs the workload or
|
||||
confirms recovery.
|
||||
|
||||
| Action | Effect |
|
||||
| --- | --- |
|
||||
| **Acknowledge** | Marks the incident as seen. While acknowledged, further firing notifications, escalation and its recovery notification are suppressed. The underlying alert can remain active. |
|
||||
| **Unacknowledge** | Removes that acknowledgement. Normal notification policies apply again; this is not a guaranteed immediate resend. |
|
||||
| **Snooze** | Pauses notifications and escalation for this incident until the selected time, including critical notifications. Monitoring can still detect recovery during the snooze. |
|
||||
| **Resume** | Ends this incident's snooze early. It does not remove an acknowledgement or clear the underlying alert. |
|
||||
|
||||
Acknowledged alerts are hidden from the default active list and excluded from
|
||||
its **Active** count. Use **Show acknowledged** to inspect them and
|
||||
**Unacknowledge** only when they need attention again. **No unacknowledged
|
||||
alerts** does not mean every workload recovered. Before **Acknowledge all**, read
|
||||
the count and scope: the toolbar applies to all unacknowledged active alerts;
|
||||
a group's button applies to that group, including its collapsed related alerts.
|
||||
Use the individual action when only one incident has been reviewed.
|
||||
|
||||
Choose a **Snooze** duration on the incident's card and check its **Snoozed until**
|
||||
time. **Until tomorrow at 9:00** uses the browser's local timezone, not the
|
||||
[quiet-hours timezone](#quiet-hours-and-notification-holds). On expiry or
|
||||
**Resume**, an acknowledged incident remains acknowledged; remove that hold
|
||||
separately if intended. Other schedules, routing, cooldowns and destination
|
||||
settings still apply, so ending a snooze is not proof of delivery or a promise
|
||||
to replay missed notifications.
|
||||
|
||||
These incident actions are not **Dismiss retained failures** or **Retry retained
|
||||
deliveries**. They do not retry a failed notification. Check fresh readings and
|
||||
the workload for recovery, and [Recent delivery activity](TROUBLESHOOTING.md#test-succeeds-but-real-alerts-are-missing)
|
||||
for delivery. Do not create an outage or lower thresholds to test an action.
|
||||
|
||||
### Quiet hours and notification holds
|
||||
|
||||
Open **Alerts → Schedule → Quiet hours** to enable a notification quiet period.
|
||||
|
||||
@@ -623,8 +623,10 @@ alert was generated, routed or delivered to the intended recipient.
|
||||
Turn delivery on there only when you intend to send alerts.
|
||||
- Check the affected alert, the destination's **Enabled** state, minimum alert
|
||||
severity and tag filters. Review the [quiet-hours schedule](CONFIGURATION.md#quiet-hours-and-notification-holds)
|
||||
and any mute, acknowledgement or maintenance policy before treating an absent
|
||||
attempt as a transport failure.
|
||||
and [acknowledgement or snooze](CONFIGURATION.md#acknowledge-and-snooze-existing-alerts),
|
||||
plus any mute or maintenance policy, before treating an absent attempt as a
|
||||
transport failure. In **Alerts → Overview**, use **Show acknowledged** to find
|
||||
hidden incidents; **Resume** ends a snooze but does not remove acknowledgement.
|
||||
- Use **Recent delivery activity** to correlate the original alert, destination
|
||||
and absolute timestamp, including held-notification reasons. An empty window
|
||||
is not proof of healthy delivery; an **unavailable** read is not an empty log.
|
||||
|
||||
@@ -147,7 +147,7 @@ describe('useAlertOverviewState', () => {
|
||||
acknowledged: makeAlert('acknowledged', new Date(now - 2 * 60_000).toISOString(), true),
|
||||
old: makeAlert('old', new Date(now - 3 * 86_400_000).toISOString(), false),
|
||||
});
|
||||
const [showAcknowledged] = createSignal(false);
|
||||
const [showAcknowledged, setShowAcknowledged] = createSignal(false);
|
||||
const updateAlert = vi.fn();
|
||||
|
||||
vi.mocked(AlertsAPI.acknowledge).mockResolvedValue(undefined as any);
|
||||
@@ -185,6 +185,17 @@ describe('useAlertOverviewState', () => {
|
||||
);
|
||||
expect(notificationStore.success).toHaveBeenCalledWith('Alert acknowledged');
|
||||
expect(result.processingAlerts().has('warning')).toBe(true);
|
||||
// Hiding/counting acknowledgement is not deletion or measured recovery.
|
||||
expect(result.alertStats()).toMatchObject({ active: 1, acknowledged: 2 });
|
||||
expect(result.filteredAlerts().map((alert) => alert.id)).toEqual(['old']);
|
||||
expect(Object.keys(activeAlerts())).toHaveLength(3);
|
||||
setShowAcknowledged(true);
|
||||
expect(result.filteredAlerts().map((alert) => alert.id)).toEqual([
|
||||
'old',
|
||||
'warning',
|
||||
'acknowledged',
|
||||
]);
|
||||
setShowAcknowledged(false);
|
||||
|
||||
vi.advanceTimersByTime(1500);
|
||||
expect(result.processingAlerts().has('warning')).toBe(false);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { renderDocMarkdown } from '@/features/docs/docMarkdown';
|
||||
import { snoozePresetExpiry } from '@/features/alerts/AlertSnoozeAction';
|
||||
import {
|
||||
getAlertOverviewAcknowledgedToggleLabel,
|
||||
getAlertOverviewPrimaryActionLabel,
|
||||
getAlertOverviewResumeLabel,
|
||||
getAlertOverviewSnoozeOptionLabel,
|
||||
} from '@/utils/alertOverviewPresentation';
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../..');
|
||||
const read = (name: string): string => readFileSync(path.join(root, name), 'utf8');
|
||||
|
||||
function article(name: string) {
|
||||
const element = document.createElement('article');
|
||||
element.innerHTML = renderDocMarkdown(read(`docs/${name}.md`), name);
|
||||
return element;
|
||||
}
|
||||
|
||||
function actions() {
|
||||
const source = read('docs/CONFIGURATION.md')
|
||||
.split('### Acknowledge and snooze existing alerts')[1]
|
||||
?.split('### Quiet hours and notification holds')[0];
|
||||
expect(source, 'existing incident controls need an explanation of their holds').toBeDefined();
|
||||
const element = document.createElement('article');
|
||||
element.innerHTML = renderDocMarkdown(source!, 'CONFIGURATION');
|
||||
return element;
|
||||
}
|
||||
|
||||
describe('existing alert incident actions help', () => {
|
||||
it('renders the actual control names and keeps notification holds separate from recovery', () => {
|
||||
const rows = [...actions().querySelectorAll('tbody tr')].map((row) =>
|
||||
row.textContent?.replace(/\s+/g, ' '),
|
||||
);
|
||||
expect(rows).toHaveLength(4);
|
||||
expect(rows[0]).toContain(
|
||||
getAlertOverviewPrimaryActionLabel({ acknowledged: false, processing: false }),
|
||||
);
|
||||
expect(rows[0]).toContain('firing notifications, escalation and its recovery notification');
|
||||
expect(rows[0]).toContain('underlying alert can remain active');
|
||||
expect(rows[1]).toContain(
|
||||
getAlertOverviewPrimaryActionLabel({ acknowledged: true, processing: false }),
|
||||
);
|
||||
expect(rows[1]).toContain('not a guaranteed immediate resend');
|
||||
expect(rows[2]).toContain('including critical notifications');
|
||||
expect(rows[3]).toContain(getAlertOverviewResumeLabel());
|
||||
expect(rows[3]).toContain('does not remove an acknowledgement');
|
||||
});
|
||||
|
||||
it('explains hidden incidents and bulk scope rather than an all-clear', () => {
|
||||
const text = actions().textContent!.replace(/\s+/g, ' ');
|
||||
expect(text).toContain('neither action repairs the workload or confirms recovery');
|
||||
expect(text).toContain(
|
||||
'hidden from the default active list and excluded from its Active count',
|
||||
);
|
||||
expect(text).toContain(getAlertOverviewAcknowledgedToggleLabel(false));
|
||||
expect(text).toContain('does not mean every workload recovered');
|
||||
expect(text).toContain('toolbar applies to all unacknowledged active alerts');
|
||||
expect(text).toContain('including its collapsed related alerts');
|
||||
expect(text).toContain('Use the individual action when only one incident has been reviewed');
|
||||
});
|
||||
|
||||
it('binds tomorrow morning to the real local-calendar preset and preserves separate holds', () => {
|
||||
const text = actions().textContent!.replace(/\s+/g, ' ');
|
||||
expect(text).toContain(getAlertOverviewSnoozeOptionLabel('tomorrowMorning'));
|
||||
expect(text).toContain("browser's local timezone, not the quiet-hours timezone");
|
||||
// Deliberately use local constructors: the preset uses the browser's date,
|
||||
// not a UTC duration or the alert schedule's independently configured zone.
|
||||
const expiry = snoozePresetExpiry('tomorrowMorning', new Date(2026, 9, 7, 22, 30));
|
||||
expect(expiry.getFullYear()).toBe(2026);
|
||||
expect(expiry.getMonth()).toBe(9);
|
||||
expect(expiry.getDate()).toBe(8);
|
||||
expect(expiry.getHours()).toBe(9);
|
||||
expect(expiry.getMinutes()).toBe(0);
|
||||
expect(text).toContain('an acknowledged incident remains acknowledged');
|
||||
expect(text).toContain('not proof of delivery or a promise to replay missed notifications');
|
||||
expect(text).toContain('They do not retry a failed notification');
|
||||
expect(text).toContain('Do not create an outage or lower thresholds');
|
||||
});
|
||||
|
||||
it('ships exact guidance with working diagnosis and schedule links', () => {
|
||||
for (const name of ['CONFIGURATION', 'TROUBLESHOOTING']) {
|
||||
expect(read(`frontend-modern/public/docs/${name}.md`)).toBe(read(`docs/${name}.md`));
|
||||
}
|
||||
for (const [from, to, fragment, label] of [
|
||||
[
|
||||
'TROUBLESHOOTING',
|
||||
'CONFIGURATION',
|
||||
'acknowledge-and-snooze-existing-alerts',
|
||||
'acknowledgement or snooze',
|
||||
],
|
||||
[
|
||||
'CONFIGURATION',
|
||||
'TROUBLESHOOTING',
|
||||
'test-succeeds-but-real-alerts-are-missing',
|
||||
'Recent delivery activity',
|
||||
],
|
||||
]) {
|
||||
const link = article(from).querySelector(`a[href="/docs/${to}#${fragment}"]`);
|
||||
expect(link?.textContent).toBe(label);
|
||||
expect(link?.hasAttribute('data-doc-link')).toBe(true);
|
||||
expect(article(to).querySelector(`[id="${fragment}"]`)).not.toBeNull();
|
||||
}
|
||||
expect(actions().querySelector('a[href="#quiet-hours-and-notification-holds"]')).not.toBeNull();
|
||||
expect(
|
||||
article('CONFIGURATION').querySelector('#quiet-hours-and-notification-holds'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user