f1754c583e
A blue tag with the notebook icon and a count marks an incident with working notes; a green one marks a note on what fixed it. Both let you scan the queue for incidents that have more to say than their title, without opening each one. The incident list and detail JSON gain note_count and resolution_note_count, counted in the same query that selects the incident, so a list costs no extra request per row. The fields are additive: terdut-tui and terdut-operator ignore them and need no change. Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
126 lines
5.9 KiB
Go
126 lines
5.9 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"`
|
|
|
|
// AcknowledgedByServiceAccountID/Name are the service-account-shaped
|
|
// parallel to AcknowledgedByID/User above: mutually exclusive with it,
|
|
// populated when a service account (not a human) acknowledged this
|
|
// incident. See migration 015 and terdut-server#25.
|
|
AcknowledgedByServiceAccountID *int64 `json:"acknowledged_by_service_account_id,omitempty"`
|
|
AcknowledgedByServiceAccountName *string `json:"acknowledged_by_service_account,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"`
|
|
|
|
// NoteCount and ResolutionNoteCount count the plain working notes and the
|
|
// "what fixed it" notes on the timeline, so a list can flag the incidents
|
|
// that carry extra information.
|
|
NoteCount int `json:"note_count"`
|
|
ResolutionNoteCount int `json:"resolution_note_count"`
|
|
|
|
// 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, archived, unarchived, snoozed, unsnoozed,
|
|
// resolved, note. UserID and
|
|
// ServiceAccountID are mutually exclusive; both nil means the server acted
|
|
// rather than any caller.
|
|
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"`
|
|
|
|
// ServiceAccountID/Name are the service-account-shaped parallel to
|
|
// UserID/Username above: mutually exclusive with it, populated when a
|
|
// service account (not a human, and not nil-meaning-the-server-acted)
|
|
// performed this event. Named Name, not Username — a ServiceAccount has
|
|
// a Name field, not a Username. See migration 015 and terdut-server#25.
|
|
ServiceAccountID *int64 `json:"service_account_id,omitempty"`
|
|
ServiceAccountName *string `json:"service_account_name,omitempty"`
|
|
|
|
// Actor* name who performed an 'assigned' event, whose UserID is the
|
|
// assignee. Mutually exclusive; unset on every other event type and on
|
|
// assignments made before migration 018. See terdut-server#35.
|
|
ActorUserID *int64 `json:"actor_user_id,omitempty"`
|
|
ActorUsername *string `json:"actor_username,omitempty"`
|
|
ActorServiceAccountID *int64 `json:"actor_service_account_id,omitempty"`
|
|
ActorServiceAccountName *string `json:"actor_service_account_name,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"`
|
|
}
|