60ebb75cd2
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
94 lines
4.1 KiB
Go
94 lines
4.1 KiB
Go
package models
|
|
|
|
import "time"
|
|
|
|
// Incident is the human work item: the thing that gets acknowledged, assigned,
|
|
// snoozed, discussed and resolved. Alerts are the machine-owned signal records
|
|
// underneath it — many alerts map to one incident, correlated by the groupKey
|
|
// Alertmanager already computed from the operator's group_by configuration.
|
|
//
|
|
// Nothing here is ever written by the Alertmanager webhook except Status, which
|
|
// the webhook and the sweeper may flip to "resolved" once every member alert has
|
|
// stopped firing.
|
|
type Incident struct {
|
|
// EscalationLevel is which rung of its team's ladder this incident is on,
|
|
// 0 for none — either the team has no ladder, or somebody has answered.
|
|
// EscalationDueAt is when the current level runs out, so a client can say
|
|
// how long is left rather than only what already happened.
|
|
EscalationLevel int64 `json:"escalation_level"`
|
|
EscalationDueAt *time.Time `json:"escalation_due_at,omitempty"`
|
|
|
|
// TeamID is the team that owns this incident, fixed when it opens: an
|
|
// incident never moves between teams. TeamName rides along so the combined
|
|
// queue can badge each row without a second request.
|
|
TeamID int64 `json:"team_id"`
|
|
TeamName string `json:"team_name,omitempty"`
|
|
|
|
ID int64 `json:"id"`
|
|
GroupKey string `json:"group_key"`
|
|
Title string `json:"title"`
|
|
GroupLabels map[string]string `json:"group_labels"`
|
|
|
|
// Status is "triggered", "acknowledged" or "resolved".
|
|
Status string `json:"status"`
|
|
|
|
// Severity is the highest `severity` label across the alerts that were
|
|
// firing when it was last recomputed. It is deliberately not cleared when an
|
|
// incident resolves — a resolved incident should still say how bad it was.
|
|
Severity *string `json:"severity,omitempty"`
|
|
|
|
TriggeredAt time.Time `json:"triggered_at"`
|
|
|
|
AcknowledgedByID *int64 `json:"acknowledged_by_id,omitempty"`
|
|
AcknowledgedByUser *string `json:"acknowledged_by,omitempty"`
|
|
AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"`
|
|
|
|
AssignedToID *int64 `json:"assigned_to_id,omitempty"`
|
|
AssignedToUser *string `json:"assigned_to,omitempty"`
|
|
|
|
// SnoozedUntil hides the incident from the default queue without closing it.
|
|
// A timestamp in the past reads as "not snoozed"; nothing sweeps it.
|
|
SnoozedUntil *time.Time `json:"snoozed_until,omitempty"`
|
|
|
|
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
|
|
|
|
// ResolutionSource is "alerts" when every member alert stopped firing, or
|
|
// "manual" when a human closed it. Manual resolution is terminal: a later
|
|
// occurrence opens a new incident rather than reopening this one.
|
|
ResolutionSource *string `json:"resolution_source,omitempty"`
|
|
|
|
ArchivedAt *time.Time `json:"archived_at,omitempty"`
|
|
|
|
// Alerts is populated by GET /api/incidents/{id} only.
|
|
Alerts []Alert `json:"alerts,omitempty"`
|
|
}
|
|
|
|
// IncidentEvent is one entry in an incident's timeline. The table is append-only
|
|
// and is the only history this server keeps — alert rows are mutated in place.
|
|
//
|
|
// Type is one of: triggered, alert_added, alert_resolved, acknowledged,
|
|
// unacknowledged, assigned, snoozed, unsnoozed, resolved, note. A nil UserID
|
|
// means the server acted rather than a person.
|
|
type IncidentEvent struct {
|
|
ID int64 `json:"id"`
|
|
IncidentID int64 `json:"incident_id"`
|
|
Type string `json:"type"`
|
|
UserID *int64 `json:"user_id,omitempty"`
|
|
Username *string `json:"username,omitempty"`
|
|
AlertID *int64 `json:"alert_id,omitempty"`
|
|
Detail *string `json:"detail,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// SimilarIncident is an earlier, resolved incident with the same signature as
|
|
// the one being looked at. ResolutionNotes are the "what fixed it" notes;
|
|
// NoteCount counts the plain working notes, which live on the timeline.
|
|
type SimilarIncident struct {
|
|
ID int64 `json:"id"`
|
|
Title string `json:"title"`
|
|
TriggeredAt time.Time `json:"triggered_at"`
|
|
ResolvedAt time.Time `json:"resolved_at"`
|
|
NoteCount int `json:"note_count"`
|
|
ResolutionNotes []IncidentEvent `json:"resolution_notes"`
|
|
}
|