Skip to content

feat: add reverse span links, show span link detail - #3011

Open
karl-power wants to merge 1 commit into
mainfrom
reverse-span-links
Open

feat: add reverse span links, show span link detail#3011
karl-power wants to merge 1 commit into
mainfrom
reverse-span-links

Conversation

@karl-power

@karl-power karl-power commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Span links were only usable from the consumer side: a span showed the links it carries, as bare "Open trace" actions. There was no way to start from a producer span and find the consumer spans that link back to it.

  • New "Linked from" section in the span Overview panel listing spans whose links reference the selected span. Each entry shows the referencing span's name, service, duration, and timestamp, with the same open-trace navigation as forward links.
  • The existing Span Links section now resolves each link's target span and shows the same details instead of a bare "Open trace" action (which remains the fallback when the target can't be found).
  • Since forward and reverse links come in pairs, clicking a link that targets the span you just hopped from pops back to the previous breadcrumb instead of growing the trail forever on A ↔ B hops. SourceFrame gains an optional, source-agnostic originRowId for this.
  • Both lookups run through useQueriedChartConfig bounded to a time window anchored on the selected span, skewed in the direction links point in time: referencing spans run at/after the span (−1h/+24h), linked-to spans ran at/before it (−24h/+1h).

Screenshots or video

Untitled.mov

How to test on Vercel preview

Preview routes: /search

Steps:

  1. Find a span that has some links (search Links.SpanId != [])
  2. Open the span and scroll to the span link section, navigate to the linked span.
  3. Check the reverse linking on the linked to span, as shown in the video.

References

@changeset-bot

changeset-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b8819a4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Minor
@hyperdx/api Minor
@hyperdx/otel-collector Minor

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

@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview Aug 27, 2026 1:48pm
hyperdx-storybook Ready Ready Preview Aug 27, 2026 1:48pm

Request Review

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label Aug 27, 2026
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Diff size: 661 production lines changed (Tier 2 max: < 250)

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Production files changed: 6
  • Production lines changed: 661 (+ 838 in test files, excluded from tier calculation)
  • Branch: reverse-span-links
  • Author: karl-power

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds reverse span-link discovery, resolves linked-span metadata, and improves navigation between mutually linked spans.

  • Adds bounded queries for forward and reverse span relationships.
  • Displays linked span names, services, durations, and timestamps in the overview panel.
  • Tracks origin row IDs to collapse reciprocal link hops back into the existing breadcrumb.
  • Adds focused component, query, and navigation tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/app/src/components/linkedSpans.tsx Adds shared query construction, result normalization, hooks, and metadata rendering for forward and reverse span links.
packages/app/src/components/DBRowOverviewPanel.tsx Integrates resolved forward-link details and the new reverse-link section into the row overview.
packages/app/src/components/DBRowSidePanel.tsx Adds origin-aware source-frame navigation so reciprocal span-link hops return through the existing breadcrumb.
packages/app/src/components/SpanLinksSubpanel.tsx Enhances forward links with resolved span names and metadata while preserving the unresolved fallback.
packages/app/src/components/SpanLinkedFromSubpanel.tsx Adds the reverse-link list UI and its trace-navigation actions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Selected span] --> B[Resolve forward links]
  A --> C[Query spans linking back]
  B --> D[Linked span details]
  C --> E[Linked from details]
  D --> F[Open linked trace]
  E --> F
  F --> G{Targets origin row?}
  G -->|Yes| H[Pop breadcrumb]
  G -->|No| I[Push source frame]
Loading

Reviews (3): Last reviewed commit: "feat: add reverse span links, show span ..." | Re-trigger Greptile

Comment thread packages/app/src/components/linkedSpans.tsx
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 320 passed • 1 skipped • 1185s

Status Count
✅ Passed 320
❌ Failed 0
⚠️ Flaky 3
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Deep Review

No critical issues found. The feature is wired correctly end-to-end: useRowData projects the trace/span-id and __hdx_timestamp_value_* aliases the new hooks depend on, so linkAnchorDate resolves and both lookups fire. SQL is built with SqlString.format parameterization over trace/span ids and SqlString.raw only over source-configured column expressions (config, not user input), so no injection is introduced. Result rendering is wrapped in an ErrorBoundary, rows are deduped, and the selected span is excluded from its own reverse-link list. When no usable timestamp anchor exists the queries are disabled and the forward "Span Links" section still renders its prior bare "Open trace" fallback, so there is no regression to existing behavior.

Note on coverage: of the dispatched fan-out, the performance and previous-comments reviewers returned before synthesis was required; the remaining personas did not report back in time, so the findings below come from those two reviewers plus direct verification of the correctness, security, and navigation-state paths. Treat P0/P1 absence as verified for the paths inspected, not as a full-fan-out clearance.

🔵 P3 nitpicks (4)
  • packages/app/src/components/linkedSpans.tsx:150 — The reverse-link has()/arrayExists lookup is unindexed on the default ClickStack schema and getReverseSpanLinksConfig returns non-null for any trace span, so a full directional-window scan runs on every span-detail panel open even for spans with zero incoming links (unlike the forward lookup, which is gated on validSpanLinks.length > 0).
    • Fix: Document the recommended Links.SpanId bloom_filter skipping index for operators, or gate/narrow the reverse window so the scan cost scales with intent.
  • packages/app/src/components/linkedSpans.tsx:166 — Both lookups silently cap at MAX_LINKED_SPANS = 50 (reverse keeps the earliest 50 by timestamp ASC; forward keeps the first 50 deduped pairs), so a span with more than 50 links shows a partial set with no truncation indicator.
    • Fix: Surface a "showing first 50" affordance, or document the cap where the list renders so the omission is not silent.
  • packages/app/src/components/DBRowSidePanel.tsx:632 — The pop-back check compares the always-both-ids rowId against traceSpanRowId, which omits a clause when only one of the trace/span expression-or-value is present, so the reciprocal A↔B pop-back silently fails to fire in that partial case and the breadcrumb trail grows instead (no crash, benign degradation).
    • Fix: Build rowId and traceSpanRowId from the same clause set so equality holds whenever both frames describe the same row.
  • packages/app/src/components/DBRowSidePanel.types.ts:44 — Typo somtimes in the originRowId doc comment.
    • Fix: Correct to sometimes.

Reviewers (2 completed): performance, previous-comments — plus direct orchestrator verification of correctness, SQL-safety, and breadcrumb navigation state.

Testing gaps: No latency benchmark of the unindexed reverse has()/arrayExists scan against a large, default (unindexed) otel_traces table to confirm interactive panel-open cost at production scale.

@pulpdrew pulpdrew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, and seems to work well.

Left a suggestion for another option if we're worried about the performance impact.

Comment on lines +95 to +110
...(source.kind === SourceKind.Trace && source.serviceNameExpression
? [
{
valueExpression: source.serviceNameExpression,
alias: LINKED_SPAN_ALIASES.SERVICE_NAME,
},
]
: []),
...(source.kind === SourceKind.Trace && source.durationExpression
? [
{
valueExpression: getDurationMsExpression(source),
alias: LINKED_SPAN_ALIASES.DURATION_MS,
},
]
: []),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: It might be nice to update some of these types to source: TTraceSource; so these checks aren't necessary, assuming we should typically only be working with trace sources in most of these functions.

Comment on lines +146 to +147
// (a scan of ~2 daily partitions). If that gets slow (~1B spans/day),
// adding to otel_traces:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option here is to gate this behind a button, say "Show spans linking to this span" in the UI to dispatch a potentially slow search, instead of always dispatching it when opening the panel

Comment on lines +628 to +631
// A link often points right back at the row one level up (forward and
// reverse span links come in pairs). Pop back to that breadcrumb instead
// of pushing an endless A <-> B trail. Only when no nav drilldown sits
// on top: popOne would pop the nav entry, not the source frame.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is neat

@pulpdrew

Copy link
Copy Markdown
Contributor

One other callout is that it may be good to add an E2E test for this functionality, to prevent regressions in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Show span links from the producer side too

2 participants