mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 18:45:53 +00:00
Keep delivery warnings concise on overview
Change-source: pulse-maintainer
This commit is contained in:
@@ -102,4 +102,39 @@ describe('AlertDeliveryHealthCard', () => {
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('send a test before relying on delivery');
|
||||
expect(screen.getByRole('button', { name: 'Refresh delivery status' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('keeps the overview treatment concise and points directly to delivery evidence', () => {
|
||||
render(() => (
|
||||
<Router>
|
||||
<Route
|
||||
path="/"
|
||||
component={() => (
|
||||
<AlertDeliveryHealthCard
|
||||
health={degradedHealth}
|
||||
unavailable={false}
|
||||
refreshing={false}
|
||||
onRefresh={vi.fn()}
|
||||
onRetryFailures={vi.fn()}
|
||||
onDismissFailures={vi.fn()}
|
||||
detailsHref="/alerts/notifications#notification-delivery-activity"
|
||||
detailLevel="summary"
|
||||
showRefresh={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Router>
|
||||
));
|
||||
|
||||
const alert = screen.getByRole('alert');
|
||||
expect(alert).toHaveTextContent('Most recent failures: authentication (2)');
|
||||
expect(alert).toHaveTextContent(
|
||||
'Review delivery activity for timestamps, destinations, alerts, and errors',
|
||||
);
|
||||
expect(alert).not.toHaveTextContent('Otherwise Pulse removes expired records hourly');
|
||||
expect(screen.queryByRole('button', { name: 'Refresh delivery status' })).toBeNull();
|
||||
expect(screen.getByRole('link', { name: 'Review delivery activity' })).toHaveAttribute(
|
||||
'href',
|
||||
'/alerts/notifications#notification-delivery-activity',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Card } from '@/components/shared/Card';
|
||||
import {
|
||||
getAlertDestinationsDeliveryDismissLabel,
|
||||
getAlertDestinationsDeliveryHealthDescription,
|
||||
getAlertDestinationsDeliveryHealthSummary,
|
||||
getAlertDestinationsDeliveryHealthTitle,
|
||||
getAlertDestinationsDeliveryRefreshLabel,
|
||||
getAlertDestinationsDeliveryReviewLabel,
|
||||
@@ -23,6 +24,8 @@ interface AlertDeliveryHealthCardProps {
|
||||
onRetryFailures?: () => void;
|
||||
onDismissFailures?: () => void;
|
||||
detailsHref?: string;
|
||||
detailLevel?: 'summary' | 'full';
|
||||
showRefresh?: boolean;
|
||||
}
|
||||
|
||||
export function AlertDeliveryHealthCard(props: AlertDeliveryHealthCardProps) {
|
||||
@@ -32,27 +35,34 @@ export function AlertDeliveryHealthCard(props: AlertDeliveryHealthCardProps) {
|
||||
const deadLetter = () => props.health?.deadLetter ?? 0;
|
||||
const completedRetentionDays = () => props.health?.completedRetentionDays ?? 7;
|
||||
const deadLetterRetentionDays = () => props.health?.deadLetterRetentionDays ?? 30;
|
||||
const description = () => {
|
||||
const input = {
|
||||
status: status(),
|
||||
failed: failed(),
|
||||
deadLetter: deadLetter(),
|
||||
completedRetentionDays: completedRetentionDays(),
|
||||
deadLetterRetentionDays: deadLetterRetentionDays(),
|
||||
failureClasses7d: props.health?.failureClasses7d,
|
||||
failureClassesAvailable: props.health?.failureClassesAvailable ?? false,
|
||||
};
|
||||
return props.detailLevel === 'summary'
|
||||
? getAlertDestinationsDeliveryHealthSummary(input)
|
||||
: getAlertDestinationsDeliveryHealthDescription(input);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card tone="danger" padding="sm" class="border-red-200 dark:border-red-800 sm:p-4" role="alert">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="flex min-w-0 items-start gap-3">
|
||||
<AlertTriangleIcon class="mt-0.5 h-4 w-4 flex-shrink-0 text-red-700 dark:text-red-300" />
|
||||
<AlertTriangleIcon
|
||||
class="mt-0.5 h-4 w-4 flex-shrink-0 text-red-700 dark:text-red-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<h3 class="text-sm font-semibold text-red-900 dark:text-red-100">
|
||||
{getAlertDestinationsDeliveryHealthTitle(status())}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm leading-6 text-red-800 dark:text-red-200">
|
||||
{getAlertDestinationsDeliveryHealthDescription({
|
||||
status: status(),
|
||||
failed: failed(),
|
||||
deadLetter: deadLetter(),
|
||||
completedRetentionDays: completedRetentionDays(),
|
||||
deadLetterRetentionDays: deadLetterRetentionDays(),
|
||||
failureClasses7d: props.health?.failureClasses7d,
|
||||
failureClassesAvailable: props.health?.failureClassesAvailable ?? false,
|
||||
})}
|
||||
</p>
|
||||
<p class="mt-1 text-sm leading-6 text-red-800 dark:text-red-200">{description()}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-shrink-0 flex-wrap items-center gap-2">
|
||||
@@ -83,15 +93,20 @@ export function AlertDeliveryHealthCard(props: AlertDeliveryHealthCardProps) {
|
||||
{getAlertDestinationsDeliveryDismissLabel()}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={props.refreshing || props.retryingFailures || props.dismissingFailures}
|
||||
onClick={props.onRefresh}
|
||||
>
|
||||
<RefreshCwIcon class={`mr-2 h-4 w-4 ${props.refreshing ? 'animate-spin' : ''}`} />
|
||||
{getAlertDestinationsDeliveryRefreshLabel()}
|
||||
</Button>
|
||||
{props.showRefresh !== false ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={props.refreshing || props.retryingFailures || props.dismissingFailures}
|
||||
onClick={props.onRefresh}
|
||||
>
|
||||
<RefreshCwIcon
|
||||
class={`mr-2 h-4 w-4 ${props.refreshing ? 'animate-spin' : ''}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{getAlertDestinationsDeliveryRefreshLabel()}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -88,7 +88,9 @@ export function OverviewTab(props: {
|
||||
dismissingFailures={deliveryHealthState.dismissingTerminalFailures()}
|
||||
onRetryFailures={() => void deliveryHealthState.retryTerminalFailures()}
|
||||
onDismissFailures={() => void deliveryHealthState.dismissTerminalFailures()}
|
||||
detailsHref="/alerts/notifications"
|
||||
detailsHref="/alerts/notifications#notification-delivery-activity"
|
||||
detailLevel="summary"
|
||||
showRefresh={false}
|
||||
/>
|
||||
</Show>
|
||||
<AlertOverviewStatsCards state={overviewState} />
|
||||
|
||||
@@ -124,5 +124,7 @@ describe('OverviewTab delivery health actions', () => {
|
||||
});
|
||||
expect(screen.getByRole('button', { name: 'Retry retained deliveries' })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: 'Dismiss retained failures' })).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: 'Refresh delivery status' })).toBeNull();
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('Most recent failures: connectivity (1).');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
getAlertDestinationsAppriseValidationError,
|
||||
getAlertDestinationsConfigLoadError,
|
||||
getAlertDestinationsDeliveryHealthDescription,
|
||||
getAlertDestinationsDeliveryHealthSummary,
|
||||
getAlertDestinationsDeliveryHealthTitle,
|
||||
getAlertDestinationsDeliveryDismissConfirmation,
|
||||
getAlertDestinationsDeliveryDismissLabel,
|
||||
@@ -204,6 +205,22 @@ describe('alertDestinationsPresentation', () => {
|
||||
'Delivery history remains available',
|
||||
);
|
||||
});
|
||||
|
||||
it('summarizes degraded delivery health without configuration guidance', () => {
|
||||
expect(
|
||||
getAlertDestinationsDeliveryHealthSummary({
|
||||
status: 'degraded',
|
||||
failed: 1,
|
||||
deadLetter: 2,
|
||||
completedRetentionDays: 7,
|
||||
deadLetterRetentionDays: 30,
|
||||
failureClasses7d: { connectivity: 3 },
|
||||
failureClassesAvailable: true,
|
||||
}),
|
||||
).toBe(
|
||||
'1 failed delivery retained for 7 days and 2 dead-lettered deliveries retained for 30 days. These notifications were not delivered. Most recent failures: connectivity (3). Review delivery activity for timestamps, destinations, alerts, and errors.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('alert destinations delivery log copy', () => {
|
||||
|
||||
@@ -168,7 +168,7 @@ export function getAlertDestinationsDeliveryHealthTitle(status: 'degraded' | 'un
|
||||
: ALERT_DESTINATIONS_DELIVERY_UNAVAILABLE_TITLE;
|
||||
}
|
||||
|
||||
export function getAlertDestinationsDeliveryHealthDescription(input: {
|
||||
type AlertDestinationsDeliveryHealthInput = {
|
||||
status: 'degraded' | 'unavailable';
|
||||
failed: number;
|
||||
deadLetter: number;
|
||||
@@ -176,11 +176,9 @@ export function getAlertDestinationsDeliveryHealthDescription(input: {
|
||||
deadLetterRetentionDays: number;
|
||||
failureClasses7d?: Record<string, number>;
|
||||
failureClassesAvailable?: boolean;
|
||||
}) {
|
||||
if (input.status === 'unavailable') {
|
||||
return 'Pulse could not verify the notification queue. Review the destination settings below and send a test before relying on delivery.';
|
||||
}
|
||||
};
|
||||
|
||||
function getRetainedDeliveryOutcomes(input: AlertDestinationsDeliveryHealthInput) {
|
||||
const outcomes: string[] = [];
|
||||
if (input.failed > 0) {
|
||||
outcomes.push(
|
||||
@@ -192,7 +190,38 @@ export function getAlertDestinationsDeliveryHealthDescription(input: {
|
||||
`${input.deadLetter} dead-lettered ${input.deadLetter === 1 ? 'delivery' : 'deliveries'} retained for ${input.deadLetterRetentionDays} days`,
|
||||
);
|
||||
}
|
||||
const summary = outcomes.join(' and ') || 'A retained terminal delivery failure';
|
||||
return outcomes.join(' and ') || 'A retained terminal delivery failure';
|
||||
}
|
||||
|
||||
function getDominantDeliveryFailure(input: AlertDestinationsDeliveryHealthInput) {
|
||||
if (!input.failureClassesAvailable || !input.failureClasses7d) return undefined;
|
||||
return Object.entries(input.failureClasses7d)
|
||||
.filter(([, count]) => count > 0)
|
||||
.sort((left, right) => right[1] - left[1])[0];
|
||||
}
|
||||
|
||||
export function getAlertDestinationsDeliveryHealthSummary(
|
||||
input: AlertDestinationsDeliveryHealthInput,
|
||||
) {
|
||||
if (input.status === 'unavailable') {
|
||||
return 'Pulse could not verify the notification queue. Review notification delivery before relying on it.';
|
||||
}
|
||||
|
||||
const dominant = getDominantDeliveryFailure(input);
|
||||
const diagnosis = dominant
|
||||
? ` Most recent failures: ${dominant[0].replace('_', ' ')} (${dominant[1]}).`
|
||||
: '';
|
||||
return `${getRetainedDeliveryOutcomes(input)}. These notifications were not delivered.${diagnosis} Review delivery activity for timestamps, destinations, alerts, and errors.`;
|
||||
}
|
||||
|
||||
export function getAlertDestinationsDeliveryHealthDescription(
|
||||
input: AlertDestinationsDeliveryHealthInput,
|
||||
) {
|
||||
if (input.status === 'unavailable') {
|
||||
return 'Pulse could not verify the notification queue. Review the destination settings below and send a test before relying on delivery.';
|
||||
}
|
||||
|
||||
const summary = getRetainedDeliveryOutcomes(input);
|
||||
const guidanceByClass: Record<string, string> = {
|
||||
authentication: 'Check destination credentials, tokens, and account permissions.',
|
||||
rate_limited: 'Check provider rate limits and reduce delivery volume before retrying.',
|
||||
@@ -203,13 +232,9 @@ export function getAlertDestinationsDeliveryHealthDescription(input: {
|
||||
unknown: 'Review the local notification audit details for the terminal error.',
|
||||
};
|
||||
let diagnostic = 'Check each enabled destination and send a test.';
|
||||
if (input.failureClassesAvailable && input.failureClasses7d) {
|
||||
const dominant = Object.entries(input.failureClasses7d)
|
||||
.filter(([, count]) => count > 0)
|
||||
.sort((left, right) => right[1] - left[1])[0];
|
||||
if (dominant) {
|
||||
diagnostic = `Most recent terminal failures were classified as ${dominant[0].replace('_', ' ')} (${dominant[1]}). ${guidanceByClass[dominant[0]] ?? guidanceByClass.unknown}`;
|
||||
}
|
||||
const dominant = getDominantDeliveryFailure(input);
|
||||
if (dominant) {
|
||||
diagnostic = `Most recent terminal failures were classified as ${dominant[0].replace('_', ' ')} (${dominant[1]}). ${guidanceByClass[dominant[0]] ?? guidanceByClass.unknown}`;
|
||||
}
|
||||
return `${summary}. These notifications were not delivered. ${diagnostic} Review delivery activity in Notifications for timestamps, destinations, alerts, and safely redacted errors. After correcting the destination, retry them. Dismiss retained failures to clear this warning without deleting delivery history. Otherwise Pulse removes expired records hourly after their retention limit. Recoverable retry attempts do not trigger this warning.`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user