Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/internal/api/handlers/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func AcknowledgeAlert(
via = models.AcknowledgmentViaAPI
}

if err := services.AcknowledgeAlertWithTimeline(id, req.UserName, via, escalationEngine, incidentRepo, timelineRepo); err != nil {
if err := services.AcknowledgeAlertWithTimeline(c.Request.Context(), id, req.UserName, via, escalationEngine, incidentRepo, timelineRepo); err != nil {
slog.ErrorContext(c.Request.Context(), "failed to acknowledge alert",
"alert_id", id,
"error", err,
Expand Down
7 changes: 4 additions & 3 deletions backend/internal/api/handlers/alerts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -71,7 +72,7 @@ func TestAcknowledgeAlert_Success(t *testing.T) {
StartedAt: time.Now(),
ReceivedAt: time.Now(),
}
require.NoError(t, alertRepo.Create(alert))
require.NoError(t, alertRepo.Create(context.Background(), alert))

acknowledged := false
engine := &mockEscalationEngineForHandler{
Expand Down Expand Up @@ -173,7 +174,7 @@ func TestAcknowledgeAlert_MissingUserName(t *testing.T) {
StartedAt: time.Now(),
ReceivedAt: time.Now(),
}
require.NoError(t, alertRepo.Create(alert))
require.NoError(t, alertRepo.Create(context.Background(), alert))

w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
Expand Down Expand Up @@ -211,7 +212,7 @@ func TestAcknowledgeAlert_DefaultViaIsAPI(t *testing.T) {
StartedAt: time.Now(),
ReceivedAt: time.Now(),
}
require.NoError(t, alertRepo.Create(alert))
require.NoError(t, alertRepo.Create(context.Background(), alert))

// Omit acknowledged_via — should default to "api"
body := []byte(`{"user_name":"bob"}`)
Expand Down
17 changes: 9 additions & 8 deletions backend/internal/api/handlers/incidents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
Expand Down Expand Up @@ -203,7 +204,7 @@ func TestListIncidents(t *testing.T) {

// Create test incidents
for _, incident := range tt.setupIncidents {
require.NoError(t, incidentRepo.Create(&incident), "Failed to create test incident")
require.NoError(t, incidentRepo.Create(context.Background(), &incident), "Failed to create test incident")
}

// Create test router
Expand Down Expand Up @@ -360,7 +361,7 @@ func TestGetIncident(t *testing.T) {

// Create test incident if provided
if tt.setupIncident != nil {
require.NoError(t, incidentRepo.Create(tt.setupIncident), "Failed to create test incident")
require.NoError(t, incidentRepo.Create(context.Background(), tt.setupIncident), "Failed to create test incident")

// Create timeline entry for incident creation
timelineEntry := &models.TimelineEntry{
Expand All @@ -372,12 +373,12 @@ func TestGetIncident(t *testing.T) {
ActorID: "test-user",
Content: models.JSONB{"trigger": "manual"},
}
require.NoError(t, timelineRepo.Create(timelineEntry), "Failed to create timeline entry")
require.NoError(t, timelineRepo.Create(context.Background(), timelineEntry), "Failed to create timeline entry")

// Create and link alerts if provided
for _, alert := range tt.setupAlerts {
require.NoError(t, alertRepo.Create(&alert), "Failed to create alert")
require.NoError(t, incidentRepo.LinkAlert(tt.setupIncident.ID, alert.ID, "system", "test"),
require.NoError(t, alertRepo.Create(context.Background(), &alert), "Failed to create alert")
require.NoError(t, incidentRepo.LinkAlert(context.Background(), tt.setupIncident.ID, alert.ID, "system", "test"),
"Failed to link alert to incident")
}
}
Expand Down Expand Up @@ -725,7 +726,7 @@ func TestUpdateIncident(t *testing.T) {

// Create test incident if provided
if tt.setupIncident != nil {
require.NoError(t, incidentRepo.Create(tt.setupIncident), "Failed to create test incident")
require.NoError(t, incidentRepo.Create(context.Background(), tt.setupIncident), "Failed to create test incident")
}

// Create test router
Expand Down Expand Up @@ -789,7 +790,7 @@ func TestIncidentStatusTransitions(t *testing.T) {
CreatedByID: "test-user",
TriggeredAt: time.Now(),
}
require.NoError(t, incidentRepo.Create(incident))
require.NoError(t, incidentRepo.Create(context.Background(), incident))

// Create router
router := gin.New()
Expand Down Expand Up @@ -840,7 +841,7 @@ func TestIncidentStatusTransitions(t *testing.T) {
CreatedByID: "test-user",
TriggeredAt: time.Now(),
}
require.NoError(t, incidentRepo.Create(incident))
require.NoError(t, incidentRepo.Create(context.Background(), incident))

// Create router
router := gin.New()
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/api/handlers/neuri.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func ReceiveNeuriResult(
return
}

if _, err := incidentRepo.GetByID(incidentID); err != nil {
if _, err := incidentRepo.GetByID(c.Request.Context(), incidentID); err != nil {
if _, ok := err.(*repository.NotFoundError); ok {
dto.NotFound(c, "incident", req.IncidentID)
return
Expand Down Expand Up @@ -128,7 +128,7 @@ func TriggerNeuriInvestigation(
return
}

incident, err := incidentRepo.GetByID(incidentID)
incident, err := incidentRepo.GetByID(c.Request.Context(), incidentID)
if err != nil {
if _, ok := err.(*repository.NotFoundError); ok {
dto.NotFound(c, "incident", req.IncidentID)
Expand Down
23 changes: 12 additions & 11 deletions backend/internal/api/handlers/neuri_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
Expand All @@ -18,9 +19,9 @@ import (
// ── stubs ─────────────────────────────────────────────────────────────────────

type stubNeuriRepo struct {
created []*models.NeuriResult
listResp []models.NeuriResult
listErr error
created []*models.NeuriResult
listResp []models.NeuriResult
listErr error
createErr error
}

Expand All @@ -42,7 +43,7 @@ type stubIncidentRepoForNeuri struct {
err error
}

func (s *stubIncidentRepoForNeuri) GetByID(_ uuid.UUID) (*models.Incident, error) {
func (s *stubIncidentRepoForNeuri) GetByID(_ context.Context, _ uuid.UUID) (*models.Incident, error) {
if s.err != nil {
return nil, s.err
}
Expand All @@ -59,8 +60,8 @@ type fullIncidentRepoForNeuri struct {
stub *stubIncidentRepoForNeuri
}

func (f *fullIncidentRepoForNeuri) GetByID(id uuid.UUID) (*models.Incident, error) {
return f.stub.GetByID(id)
func (f *fullIncidentRepoForNeuri) GetByID(ctx context.Context, id uuid.UUID) (*models.Incident, error) {
return f.stub.GetByID(ctx, id)
}

func neuriRouter(incRepo repository.IncidentRepository, neuriRepo repository.NeuriResultRepository) *gin.Engine {
Expand Down Expand Up @@ -156,11 +157,11 @@ func TestListNeuriResults_HappyPath(t *testing.T) {
neuriRepo := &stubNeuriRepo{
listResp: []models.NeuriResult{
{
ID: uuid.New(),
IncidentID: incidentID,
TopHypothesis: "CODE_CHANGE",
Confidence: 0.85,
Summary: "Deploy correlates.",
ID: uuid.New(),
IncidentID: incidentID,
TopHypothesis: "CODE_CHANGE",
Confidence: 0.85,
Summary: "Deploy correlates.",
RankedHypotheses: models.RawJSON("[]"),
},
},
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/api/handlers/prometheus_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func PrometheusWebhook(alertSvc services.AlertService) gin.HandlerFunc {
}

// Step 3: Process alerts through service layer
result, err := alertSvc.ProcessAlertmanagerPayload(&payload)
result, err := alertSvc.ProcessAlertmanagerPayload(c.Request.Context(), &payload)
if err != nil {
slog.ErrorContext(c.Request.Context(), "webhook processing failed",
"error", err,
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/api/handlers/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func SeedDemoData(
return
}

if err := coordinator.SeedDemoData(scheduleRepo, escalationRepo, routingRepo, incidentRepo, timelineRepo); err != nil {
if err := coordinator.SeedDemoData(c.Request.Context(), scheduleRepo, escalationRepo, routingRepo, incidentRepo, timelineRepo); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{"message": "failed to seed demo data: " + err.Error()}})
return
}
Expand Down
35 changes: 21 additions & 14 deletions backend/internal/api/handlers/setup_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
Expand All @@ -26,8 +27,10 @@ type mockIncidentRepoForSetup struct {
func (m *mockIncidentRepoForSetup) GetByNumber(n int) (*models.Incident, error) {
return m.GetByNumberFn(n)
}
func (m *mockIncidentRepoForSetup) Create(_ *models.Incident) error { return nil }
func (m *mockIncidentRepoForSetup) GetByID(_ uuid.UUID) (*models.Incident, error) { return nil, nil }
func (m *mockIncidentRepoForSetup) Create(_ context.Context, _ *models.Incident) error { return nil }
func (m *mockIncidentRepoForSetup) GetByID(_ context.Context, _ uuid.UUID) (*models.Incident, error) {
return nil, nil
}
func (m *mockIncidentRepoForSetup) GetBySlackChannelID(_ string) (*models.Incident, error) {
return nil, nil
}
Expand All @@ -43,16 +46,20 @@ func (m *mockIncidentRepoForSetup) GetByTeamsConversationID(_ string) (*models.I
func (m *mockIncidentRepoForSetup) List(_ repository.IncidentFilters, _ repository.Pagination) ([]models.Incident, int64, error) {
return nil, 0, nil
}
func (m *mockIncidentRepoForSetup) Update(_ *models.Incident) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateStatus(_ uuid.UUID, _ models.IncidentStatus) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateSlackChannel(_ uuid.UUID, _, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateSlackMessageTS(_ uuid.UUID, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateTeamsChannel(_ uuid.UUID, _, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateTeamsConversationID(_ uuid.UUID, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateTeamsActivityID(_ uuid.UUID, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateTeamsPostingIDs(_ uuid.UUID, _, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) LinkAlert(_ uuid.UUID, _ uuid.UUID, _, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) GetAlerts(_ uuid.UUID) ([]models.Alert, error) { return nil, nil }
func (m *mockIncidentRepoForSetup) Update(_ context.Context, _ *models.Incident) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateStatus(_ uuid.UUID, _ models.IncidentStatus) error {
return nil
}
func (m *mockIncidentRepoForSetup) UpdateSlackChannel(_ uuid.UUID, _, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateSlackMessageTS(_ uuid.UUID, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateTeamsChannel(_ uuid.UUID, _, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateTeamsConversationID(_ uuid.UUID, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateTeamsActivityID(_ uuid.UUID, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) UpdateTeamsPostingIDs(_ uuid.UUID, _, _ string) error { return nil }
func (m *mockIncidentRepoForSetup) LinkAlert(_ context.Context, _ uuid.UUID, _ uuid.UUID, _, _ string) error {
return nil
}
func (m *mockIncidentRepoForSetup) GetAlerts(_ uuid.UUID) ([]models.Alert, error) { return nil, nil }
func (m *mockIncidentRepoForSetup) GetIncidentByAlertID(_ uuid.UUID) (*models.Incident, error) {
return nil, nil
}
Expand All @@ -77,8 +84,8 @@ func (m *mockScheduleRepoForSetup) Create(_ *models.Schedule) error { return
func (m *mockScheduleRepoForSetup) GetByID(_ uuid.UUID) (*models.Schedule, error) {
return nil, nil
}
func (m *mockScheduleRepoForSetup) Update(_ *models.Schedule) error { return nil }
func (m *mockScheduleRepoForSetup) Delete(_ uuid.UUID) error { return nil }
func (m *mockScheduleRepoForSetup) Update(_ *models.Schedule) error { return nil }
func (m *mockScheduleRepoForSetup) Delete(_ uuid.UUID) error { return nil }
func (m *mockScheduleRepoForSetup) GetWithLayers(_ uuid.UUID) (*models.Schedule, error) {
return nil, nil
}
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/api/handlers/webhook_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (h *WebhookHandler) Handle(c *gin.Context) {

// Step 4: Process normalized alerts through service layer
// This is where deduplication, grouping, routing, and incident creation happen
result, err := h.alertService.ProcessNormalizedAlerts(alerts)
result, err := h.alertService.ProcessNormalizedAlerts(c.Request.Context(), alerts)
if err != nil {
slog.ErrorContext(c.Request.Context(), "webhook processing failed",
"error", err,
Expand Down
14 changes: 7 additions & 7 deletions backend/internal/coordinator/agents/postmortem.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type PostMortemAgentDeps struct {
AgentUserID uuid.UUID
AISvc interface{ IsEnabled() bool }
IncidentRepo interface {
GetByID(uuid.UUID) (*models.Incident, error)
GetByID(ctx context.Context, id uuid.UUID) (*models.Incident, error)
}
PostMortemSvc interface {
GetPostMortem(uuid.UUID) (*models.PostMortem, error)
Expand Down Expand Up @@ -71,9 +71,9 @@ func (a *PostMortemAgent) Handle(ctx context.Context, incidentID uuid.UUID) {
}

// Step 3: fetch incident
incident, err := a.deps.IncidentRepo.GetByID(incidentID)
incident, err := a.deps.IncidentRepo.GetByID(ctx, incidentID)
if err != nil {
slog.Error("post-mortem agent: failed to fetch incident", "incident_id", incidentID, "error", err)
slog.ErrorContext(ctx, "post-mortem agent: failed to fetch incident", "incident_id", incidentID, "error", err)
return
}

Expand Down Expand Up @@ -105,13 +105,13 @@ func (a *PostMortemAgent) Handle(ctx context.Context, incidentID uuid.UUID) {
slog.Info("post-mortem agent: draft created", "incident_id", incidentID, "postmortem_id", pm.ID)

// Step 7: write timeline entry
a.writeTimelineEntry(incident, pm)
a.writeTimelineEntry(ctx, incident, pm)

// Step 8: notify
a.notify(incident, pm)
}

func (a *PostMortemAgent) writeTimelineEntry(incident *models.Incident, pm *models.PostMortem) {
func (a *PostMortemAgent) writeTimelineEntry(ctx context.Context, incident *models.Incident, pm *models.PostMortem) {
if a.deps.TimelineRepo == nil {
return
}
Expand All @@ -126,8 +126,8 @@ func (a *PostMortemAgent) writeTimelineEntry(incident *models.Incident, pm *mode
"agent": "postmortem",
},
}
if err := a.deps.TimelineRepo.Create(entry); err != nil {
slog.Warn("post-mortem agent: failed to write timeline entry", "error", err)
if err := a.deps.TimelineRepo.Create(ctx, entry); err != nil {
slog.WarnContext(ctx, "post-mortem agent: failed to write timeline entry", "error", err)
}
}

Expand Down
4 changes: 3 additions & 1 deletion backend/internal/coordinator/agents/postmortem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (

type fakeIncidentRepo struct{ incident *models.Incident }

func (f *fakeIncidentRepo) GetByID(id uuid.UUID) (*models.Incident, error) { return f.incident, nil }
func (f *fakeIncidentRepo) GetByID(ctx context.Context, id uuid.UUID) (*models.Incident, error) {
return f.incident, nil
}

type fakePostMortemSvc struct {
called bool
Expand Down
4 changes: 3 additions & 1 deletion backend/internal/coordinator/demo_seeder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package coordinator

import (
"context"
"errors"
"log/slog"
"time"
Expand All @@ -27,6 +28,7 @@ func DemoDataExists(incidentRepo repository.IncidentRepository) (bool, error) {
// SeedDemoData creates a representative dataset so new installs feel populated.
// Safe to call only when DemoDataExists() returns false.
func SeedDemoData(
ctx context.Context,
scheduleRepo repository.ScheduleRepository,
escalationRepo repository.EscalationPolicyRepository,
routingRepo repository.RoutingRuleRepository,
Expand Down Expand Up @@ -135,7 +137,7 @@ func SeedDemoData(
AcknowledgedAt: &acknowledgedAt,
ResolvedAt: &resolvedAt,
}
if err := incidentRepo.Create(incident); err != nil {
if err := incidentRepo.Create(ctx, incident); err != nil {
return err
}
slog.Info("demo: created incident", "id", incident.ID, "number", incident.IncidentNumber)
Expand Down
Loading
Loading