Backend:
- Add `baseline_p50/p95/p99_latency_ns: Option<u64>` to `ServiceEdge`; omitted when None
- `get_current_topology` queries the previous time slot (same duration, one slot older)
serially after the current-window query, aggregates per-edge weighted-average latency,
and attaches the result as baseline fields on each edge
- `aggregate_baselines` computes `SUM(pXX * requests) / SUM(requests)` per client→server pair
- `EdgeTrendResponse` drops `p50_avg/p95_avg/p99_avg`; history endpoint now returns raw data points only
- Update `build_topology` signature and all call sites (incidents.rs) to accept baselines map
- Reduce duplicate topology API calls on page load by adding a loading guard
Frontend:
- Populate `edgeBaselines` from topology response `baseline_p95_latency_ns` instead of from history endpoint
- Edge color in graph and tree modes uses baseline ratio (>2× = light red, >1.5× = orange, within = green),
falling back to absolute p95 thresholds when no baseline is available
- Edge color is now latency-only; error rate coloring is for nodes only
Backend:
- New enterprise-only endpoint: GET /{org_id}/traces/service_graph/edge/history
- Returns time-series P50/P95/P99 latency with request-count-weighted baselines
- OSS returns 403; empty result returned gracefully if stream doesn't exist
Frontend — Interaction Model:
- Click-to-panel replaced with hover tooltip for edge interactions
- Tooltip shows compact stats (request count, error rate, latencies) on hover
- "Show trend" button fetches and renders a live P50/P95/P99 sparkline inline
- Tooltip stays open when moused-over; results cached per edge key
- Edge panel component retained, now includes its own latency trend chart section
Frontend — Edge Coloring:
- Edges now colored relative to 24h weighted-average P95 baseline when available
- Falls back to absolute threshold coloring when no baseline data exists
Frontend — Chart Rendering:
- Switched to SVG render mode for sharper output
- devicePixelRatio passed to chart init to fix blurry Retina display rendering
- View type switch no longer recreates chart (no chartKey increment); prevents replay of expand animation
- Tooltip handlers re-registered after every view switch for both tree and graph modes
Tests:
- Updated to reflect removed edge panel flow
- chartKey stability on view switch now asserted (was: increment expected)
- Wrap append() and record_alert() in DB transactions to fix
read-modify-write race conditions on concurrent alert correlation
- Add length/blank validation on comment endpoint (max 10,000 chars)
- Guard update_severity from emitting SeverityOverride on no-op changes
- Add TitleChanged event type with from/to/user_id fields, wire into
update_title, render in activity timeline
Incident Events:
- New `incident_events` table (JSONB array per incident, PK on `(org_id, incident_id)`)
- `IncidentEvent` struct with 12 typed variants: `Created`, `Alert`, `SeverityUpgrade`,
`SeverityOverride`, `Acknowledged`, `Resolved`, `Reopened`, `DimensionsUpgraded`,
`AssignmentChanged`, `Comment`, `AIAnalysisBegin`, `AIAnalysisComplete`
- Constructors on `IncidentEvent` (e.g. `::alert()`, `::resolved()`, `::comment()`)
- `increment_alert(&mut self)` for compaction: same alert only compacts if it's the
last event in the array, preserving interleaving (A x5, B x3, A x2)
- Severity fields use `IncidentSeverity` enum, not strings
Backend:
- Migration: `m20260214_000001_create_incident_events_table`
- DB ops: `init`, `get`, `append`, `record_alert` (with compaction), `delete`
- Emit events at all mutation points: incident creation, alert correlation,
dimensions upgrade, status change, severity change, auto-resolve
- `user_id` passed from HTTP auth headers through to all events
- `auto_resolve_stale()` now returns resolved IDs for event emission
- API: `GET /v2/{org_id}/alerts/incidents/{id}/events`
- API: `POST /v2/{org_id}/alerts/incidents/{id}/events/comment`
Frontend:
- `IncidentTimeline.vue`: vertical timeline with icon/color per event type,
alert compaction display, comment input, refresh button
- Activity tab added to `IncidentDetailDrawer.vue`
- `incidents.ts`: `getEvents()` and `postComment()` API methods
trace_id was the highest-priority correlator, creating a separate
incident per unique trace ID. A failing service with many traces
caused incident explosion instead of one unified incident.
Backend:
- Remove trace_id param from correlate_alert_to_incident()
- Remove TraceBased variant from CorrelationReason enum
- Remove TRACE: prefix from KeyType::classify()
- Remove trace_id extraction from scheduler handler
- Clean up 2 manual trigger call sites in alert.rs
Frontend:
- Remove trace_based from correlation reason types and UI
- Remove extractTraceId utility and its tests
- Remove trace-based translations from en.json
Correlation now uses only Service Discovery keys and semantic
dimension hashing. trace_id storage can be added separately.
When a query with LIMIT spans multiple partitions, the truncation at the
limit boundary used `req_size` (the total limit, e.g. 230) as the
truncation target instead of the remaining slots for the current
partition (`req_size - hits_already_returned`).
For example with limit=230:
- Partition 0: 43 hits returned → accumulated = 43
- Partition 1: 198 hits returned by DB, but only 187 slots remain (230 -
43). `truncate(230)` was a no-op since 198 < 230, so all 198 hits passed
through → total = 241 (exceeds limit by 11)
Fix: compute `allowed = req_size - (curr_res_size - total_hits)` which
is the number of remaining slots before this partition's hits are added,
then truncate to that value.
Fixes Issues:
- https://github.com/openobserve/openobserve/issues/10486
- https://github.com/openobserve/openobserve/issues/10602
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>