feat(traces): surface span links in the trace viewer#2463
feat(traces): surface span links in the trace viewer#2463alex-fedotyev wants to merge 2 commits into
Conversation
🦋 Changeset detectedLatest commit: aa6f19d 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 new "Span Links" accordion section to the span detail panel. Each link renders as a compact row with an "Open trace" action (full IDs on hover) plus trace state and attribute chips, reusing the existing cross-source navigation stack introduced in #2541.
Confidence Score: 5/5Display-only additive change with no schema migrations; all new code paths are gated and existing paths are untouched. The change is narrowly scoped: a new optional source field auto-detected on setup, a new component that mirrors an existing one, and a navigation callback that follows an already-proven pattern. All new code paths are gated (span links only fetched for trace sources with the expression set, section only renders when data is present, navigation only fires when a resolvable trace source exists). Eleven tests cover the key paths including the navigation wiring. No files require special attention. The only findings are a minor type-guard gap in SpanLinksSubpanel.tsx (TraceState not validated) and a consistency note in DBRowOverviewPanel.tsx (string literal vs constant). Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant SpanLinksSubpanel
participant RowSidePanelContext
participant DBRowSidePanelInner
participant handleSourceStackPush
participant URLState as URL / nuqs state
Note over DBRowSidePanelInner: On mount, wraps subtree with<br/>RowSidePanelContext providing<br/>onOpenLinkedTrace
DBRowSidePanelInner->>RowSidePanelContext: provide onOpenLinkedTrace
RowSidePanelContext->>SpanLinksSubpanel: onOpenTrace prop (via DBRowOverviewPanel)
User->>SpanLinksSubpanel: Click 'Open trace' on a link row
SpanLinksSubpanel->>DBRowSidePanelInner: onOpenLinkedTrace(link)
DBRowSidePanelInner->>DBRowSidePanelInner: Build rowId from TraceId+SpanId
DBRowSidePanelInner->>handleSourceStackPush: "{ sourceId, rowId, label, sourceKind }"
handleSourceStackPush->>URLState: setSourceStack([...current, newFrame])
handleSourceStackPush->>URLState: setTab('trace')
URLState-->>User: Linked span opens in same panel
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant SpanLinksSubpanel
participant RowSidePanelContext
participant DBRowSidePanelInner
participant handleSourceStackPush
participant URLState as URL / nuqs state
Note over DBRowSidePanelInner: On mount, wraps subtree with<br/>RowSidePanelContext providing<br/>onOpenLinkedTrace
DBRowSidePanelInner->>RowSidePanelContext: provide onOpenLinkedTrace
RowSidePanelContext->>SpanLinksSubpanel: onOpenTrace prop (via DBRowOverviewPanel)
User->>SpanLinksSubpanel: Click 'Open trace' on a link row
SpanLinksSubpanel->>DBRowSidePanelInner: onOpenLinkedTrace(link)
DBRowSidePanelInner->>DBRowSidePanelInner: Build rowId from TraceId+SpanId
DBRowSidePanelInner->>handleSourceStackPush: "{ sourceId, rowId, label, sourceKind }"
handleSourceStackPush->>URLState: setSourceStack([...current, newFrame])
handleSourceStackPush->>URLState: setTab('trace')
URLState-->>User: Linked span opens in same panel
Reviews (3): Last reviewed commit: "feat(traces): rewire span-link navigatio..." | 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!
E2E Test Results✅ All tests passed • 225 passed • 3 skipped • 1551s
Tests ran across 4 shards in parallel. |
|
Pushed a follow-up commit (4416036) with an alternative to the nested-drawer "Open trace" on a span link, so we can validate the navigation approach together. What it does: instead of opening each linked trace in a new stacked drawer (which builds a tower once you follow a chain of links), it reuses the inline-split trace panel from #2402 and navigates trace to trace in the same flyout, with a back stack and a breadcrumb of the spans you followed. Back pops a level; a breadcrumb crumb jumps straight to any level. The linked span auto-focuses on arrival. Scope: one optional Known gaps if we go this way: the Overview-tab "Open trace" still uses the drawer, there is no unit test yet for the nav stack, and no e2e for the multi-hop flow. I will fill those in once we pick a direction. To try it: open a trace whose span carries outgoing links, click "Open trace", and follow a couple of hops. |
|
Heads up on direction. #2541 reworks the side panel into a single drawer with a shared cross-source navigation stack (its new "View Trace" action pushes onto it) plus a shared breadcrumb component. That replaces the stacked/nested drawer pattern this PR's "Open trace" currently relies on. Rather than ship navigation that #2541 then has to unwind, I'm going to rebuild "Open trace" on that shared stack: following a span link becomes a push of the linked span's trace frame, the same way "View Trace" works for logs. That reuses the breadcrumb trail, URL persistence, and back/jump behavior for free, and lets me drop the in-place trace-nav code in DBTracePanel and the nested-drawer path entirely. The Span Links display section and the schema auto-detection stay as they are. Sequencing: let #2541 land first, then rebase this onto it and swap in the push call. I'll hold the navigation rewire until then. The display and schema parts are reviewable on their own in the meantime. |
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>
Rebase onto #2541 (single-drawer navigation) and replace the nested-drawer "Open trace" path with a push onto the same cross-source stack "View Trace" uses. Deletes the DBTracePanel in-place-nav prototype, the setOpenedLink nested panel, the breadcrumbPath/onBreadcrumbClick threading, and the DirectTraceSidePanel z-index seed that only existed to support the nested drawer. Adds onOpenLinkedTrace to RowSidePanelContext, computed in DBRowSidePanelInner from the same traceIdExpression/spanIdExpression used for "View Trace", and consumed by RowOverviewPanel -> SpanLinksSubpanel. Also fixes a restricted-import lint violation in SpanLinksSubpanel.test.tsx (parent-relative import instead of the @/ alias) hit while touching the file. Co-Authored-By: Claude <noreply@anthropic.com>
4416036 to
aa6f19d
Compare
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" jumps to the linked trace in place, through the same cross-source navigation #2541 introduced: the trace opens in the same panel, the breadcrumb trail picks up the hop, and back and the URL both reflect it. A span link is really the same kind of jump as the existing "View Trace" action (log to trace), just keyed off the link's trace and span ids instead of the current row's, so it reuses that mechanism directly instead of adding a second one.
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.
Rebased onto #2541
This branch tried two navigation approaches while #2541 (single-drawer navigation) was still in review: a nested drawer, then a separate in-place flyout prototype for feedback. Now that #2541 shipped a shared mechanism for exactly this, jumping across sources in place with one breadcrumb trail, I rebased onto it and dropped both. "Open trace" is just another caller of that mechanism now, the same as "View Trace": it pushes the linked trace onto the shared stack instead of opening a second drawer.
Test plan
make ci-lintandmake ci-unitpass: full app suite, common-utils.Implements #1593