Downtime catalog

This commit is contained in:
Marcelo
2026-05-06 00:36:48 +00:00
parent 0491237bad
commit bfc1673d89
42 changed files with 8035 additions and 1093 deletions

View File

@@ -38,6 +38,7 @@ type AlertsInboxEvent = {
status?: string | null;
shift?: string | null;
alertId?: string | null;
incidentKey?: string | null;
isUpdate?: boolean;
isAutoAck?: boolean;
};
@@ -224,29 +225,34 @@ function resolveShift(
}
function collapseAlertEvents(events: AlertsInboxEvent[]) {
const byAlert = new Map<string, AlertsInboxEvent>();
// Group by incidentKey (preferred — stable across the entire incident lifecycle)
// OR alertId (fallback — for older or non-stoppage events).
// Per group, keep AT MOST one "active" (oldest = when it first happened) and
// one "resolved" (newest = when it actually ended). Result: max 2 entries per incident.
const byGroup = new Map<string, AlertsInboxEvent>();
const passthrough: AlertsInboxEvent[] = [];
for (const ev of events) {
if (!ev.alertId) {
const groupId = ev.incidentKey ?? ev.alertId;
if (!groupId) {
passthrough.push(ev);
continue;
}
const statusKey = ev.status === "resolved" ? "resolved" : "active";
const key = `${ev.alertId}:${statusKey}`;
const existing = byAlert.get(key);
const key = `${groupId}:${statusKey}`;
const existing = byGroup.get(key);
if (!existing) {
byAlert.set(key, ev);
byGroup.set(key, ev);
continue;
}
const pickNewest = statusKey === "resolved";
const shouldReplace = pickNewest
? ev.ts.getTime() > existing.ts.getTime()
: ev.ts.getTime() < existing.ts.getTime();
if (shouldReplace) byAlert.set(key, ev);
if (shouldReplace) byGroup.set(key, ev);
}
const combined = [...passthrough, ...byAlert.values()];
const combined = [...passthrough, ...byGroup.values()];
combined.sort((a, b) => b.ts.getTime() - a.ts.getTime());
return combined;
}
@@ -325,7 +331,12 @@ export async function getAlertsInboxData(params: AlertsInboxParams) {
const rawStatus = safeString(payload?.status ?? inner?.status);
const isUpdate = safeBool(payload?.is_update ?? inner?.is_update);
const isAutoAck = safeBool(payload?.is_auto_ack ?? inner?.is_auto_ack);
if (!includeUpdates && (isUpdate || isAutoAck)) continue;
// Drop only auto-ack pings (every-10s refresh noise).
// Keep is_update events: due to a Node-RED spread inheritance pattern,
// virtually all events carry is_update=true even legitimate first-emission
// and cycle-arrival resolved events. Dedup happens via collapseAlertEvents
// grouping by incidentKey below.
if (!includeUpdates && isAutoAck) continue;
const shiftName = resolveShift(shifts, shiftOverrides, ev.ts, timeZone);
if (normalizedShift && shiftName !== normalizedShift) continue;
@@ -349,6 +360,7 @@ export async function getAlertsInboxData(params: AlertsInboxParams) {
status: statusLabel,
shift: shiftName,
alertId: safeString(payload?.alert_id ?? inner?.alert_id),
incidentKey: safeString(payload?.incidentKey ?? payload?.incident_key ?? inner?.incidentKey ?? inner?.incident_key),
isUpdate,
isAutoAck,
});