feat(traces): surface span links in the trace viewer#2463
Conversation
Show a "Span Links" section in the span detail when a span carries outgoing OpenTelemetry links. Each link renders as a compact row: an "Open trace" action, the trace state and attributes as chips, and the full Trace and Span IDs on hover. A link with neither state nor attributes collapses to a single line, and links past the first five sit behind a "Show more" toggle. "Open trace" opens the linked trace in the existing nested side panel with breadcrumb back navigation, the same flow the Surrounding Context tab uses, so it stacks above the trace drawer in both the search and direct-trace entry points. The Links column is auto-detected from the standard OTel trace schema and read through a guarded select, so nothing changes for sources that do not have it. This is a display-only field, so no source configuration UI and no external API contract are added. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 4f38c8a The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🟡 Tier 3 — StandardIntroduces new logic, modifies core functionality, or touches areas with non-trivial risk. Why this tier:
Review process: Full human review — logic, architecture, edge cases. Stats
|
Greptile SummaryThis PR surfaces OpenTelemetry span links in the trace viewer by adding a "Span Links" accordion section to the span detail panel. Each link renders as a compact row with an "Open trace" button (tooltip shows full IDs), trace state and attribute chips, and a "show more" toggle past five links; clicking "Open trace" opens the linked span in a stacked nested side panel with breadcrumb back navigation.
Confidence Score: 4/5Display-only addition with no schema migration and clean isolation via optional props; safe to merge. The change is well-scoped: auto-detection is guarded, the SQL WHERE clause for linked span lookup uses proper escaping, and z-index stacking follows the established pattern. The two findings are cosmetic — a redundant null-guard and a possible 'Span Links' header with an empty-state body when every link object fails the type filter. Neither affects correctness in any realistic data scenario. packages/app/src/components/DBRowOverviewPanel.tsx — the hasSpanLinks check and accordion defaultValue wiring are worth a second look. Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User
participant ROP as RowOverviewPanel
participant SLS as SpanLinksSubpanel
participant DRSP as DBRowSidePanel (nested)
participant CH as ClickHouse
U->>ROP: Opens span detail (Overview tab)
ROP->>CH: SELECT ... Links AS __hdx_span_links
CH-->>ROP: Row data incl. span links array
ROP->>SLS: "spanLinks={firstRow.__hdx_span_links}"
SLS->>SLS: Filter / type-check links
SLS-->>U: Render link rows (Open trace btn, chips)
U->>SLS: Click Open trace
SLS->>ROP: onOpenTrace(link)
ROP->>ROP: setOpenedLink(link) + compute openedLinkWhere via SqlString
ROP->>DRSP: "Render nested Drawer (rowId=openedLinkWhere, breadcrumbPath+1)"
DRSP->>CH: "SELECT ... WHERE SpanId=? AND TraceId=?"
CH-->>DRSP: Linked span row data
DRSP-->>U: Render linked span detail
U->>DRSP: Click breadcrumb / close
DRSP->>ROP: "onClose => setOpenedLink(null)"
ROP-->>U: Nested drawer dismissed
Reviews (1): Last reviewed commit: "feat(traces): surface span links in the ..." | Re-trigger Greptile |
| maxRows: 5, | ||
| }); | ||
|
|
||
| if (!links || links.length === 0) { |
There was a problem hiding this comment.
| const hasSpanLinks = useMemo(() => { | ||
| return ( | ||
| Array.isArray(firstRow?.__hdx_span_links) && | ||
| firstRow?.__hdx_span_links.length > 0 | ||
| ); | ||
| }, [firstRow?.__hdx_span_links]); |
There was a problem hiding this comment.
"Span Links" accordion visible with empty-state message for malformed data
hasSpanLinks is true whenever __hdx_span_links is a non-empty array, but SpanLinksSubpanel applies its own stricter type-filter inside useMemo (requires string TraceId, string SpanId, and a defined Attributes). If every object in the array passes the array check but fails the type-filter, the accordion section renders with a "Span Links" header but shows "No span links available for this trace" inside it — a contradictory UX. In practice real OTel data won't hit this, but the defensive check would be cheap: mirror the same SpanLinkData type guard in hasSpanLinks (or just reuse the filtered links array length).
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| defaultValue={[ | ||
| 'exception', | ||
| 'spanEvents', | ||
| 'spanLinks', | ||
| 'network', | ||
| 'resourceAttributes', | ||
| 'eventAttributes', |
There was a problem hiding this comment.
'spanLinks' unconditionally in accordion defaultValue
'spanLinks' is always included in the initial defaultValue array regardless of whether the span has any links. Mantine silently ignores values that don't match a rendered item, so this causes no visual bug, but it is inconsistent with the other entries ('exception', 'spanEvents') which are also always present despite their items being conditionally rendered. No change needed unless the team starts using this array for logic — just flagging for awareness.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
E2E Test Results✅ All tests passed • 200 passed • 3 skipped • 1336s
Tests ran across 4 shards in parallel. |
Summary
Span links are the OpenTelemetry way of pointing from one span to a span in a different trace: a producer/consumer hop, the source records behind a batch, a retried request. HyperDX ingests them in the standard
Linkscolumn but never surfaced them, so in fan-out and batch flows the related spans just showed up as orphans.This adds a "Span Links" section to the span detail. It renders only when the span actually has links. Each link is a compact row:
"Open trace" opens the linked trace in the existing nested side panel with breadcrumb back navigation, the same flow the Surrounding Context tab already uses, so it stacks above the trace drawer in both the search-results and the direct-trace entry points.
The
Linkscolumn is auto-detected from the standard OTel trace schema and read through a guarded select, so sources without it are untouched and the rest of the row-detail panel keeps working. This is a display-only field, so it adds no source-configuration UI and no external API surface.Replaces #2440
Fresh branch replacing #2440, which I'm closing. That branch's history got tangled during a rebase. This one ships the same feature with a clean diff and trims the scope to what a display-only field actually needs: it drops the source-form field, the onboarding default, and the external-API and OpenAPI entries that the earlier draft threaded through.
Test plan
make ci-lintandmake ci-unitpass (full app suite plus common-utils). 9 new unit tests cover the compact rows: empty and malformed input, attribute chips, the trace-state chip, single-line collapse, and the "Open trace" callback.Implements #1593