Show notes from similar earlier incidents

Each incident gets a signature: the alert name plus the group labels that
say what is broken, minus the ones that only say where it ran (instance,
pod, container, ...). GET /api/incidents/{id}/similar returns resolved
incidents in the same team with the same signature that have notes.

Notes can be marked as the resolution note, "what fixed it", either with a
resolution field on resolve or pinned on a note. Those lead the similar
list, show on the incident page as "Seen before", and the triggered
notification carries the latest one.

Claude-Session: https://claude.ai/code/session_01MMados3BD1oSjevHxbmVqU
This commit is contained in:
Niklas Ye
2026-09-25 15:42:25 +02:00
parent 734cd9c5fd
commit 60ebb75cd2
12 changed files with 400 additions and 23 deletions
+21
View File
@@ -330,6 +330,16 @@ func deliver(ctx context.Context, db *sql.DB, cfg NotifyConfig, n outboxRow) err
msg := renderNotification(inc, n, firing, cfg)
// The page that opens an incident carries what fixed it last time, so the
// person woken up starts from that. Best effort: a failed lookup must not
// hold back the page itself.
if n.kind == notifyTriggered {
if sim, err := similarIncidents(ctx, db, n.incidentID, 1); err == nil && len(sim) > 0 && len(sim[0].ResolutionNotes) > 0 {
notes := sim[0].ResolutionNotes
msg.Message += "\nLast time: " + shorten(derefString(notes[len(notes)-1].Detail), 160)
}
}
// An Acknowledge button needs both a user to attribute the acknowledgement
// to and a URL the phone can reach. Minted per delivery, so every push
// carries its own short-lived token rather than reusing one.
@@ -573,6 +583,17 @@ func plural(n int) string {
return "s"
}
// shorten cuts s to at most n runes, marking the cut, and flattens newlines so
// a multi-line note stays one line in a push.
func shorten(s string, n int) string {
s = strings.Join(strings.Fields(s), " ")
r := []rune(s)
if len(r) <= n {
return s
}
return string(r[:n-1]) + "…"
}
// derefString reads a nullable text column as a plain string.
func derefString(s *string) string {
if s == nil {