Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9ad4723
ref(traces): Extract isPartialSpanOrTraceData to shared utility
nsdeschenes Apr 7, 2026
4409cec
feat(traces): Disable trace links older than 30 days on high-traffic …
nsdeschenes Apr 7, 2026
7a47352
feat(traces): Disable trace links older than 30 days on insights and …
nsdeschenes Apr 7, 2026
06a3ac6
feat(traces): Disable trace links older than 30 days on profiling and…
nsdeschenes Apr 7, 2026
67b2e74
ref(traces): Remove unused export from TRACE_DATA_RETENTION_DAYS
nsdeschenes Apr 7, 2026
36d92f8
fix(traces): Resolve tsgo type errors in trace utilities
nsdeschenes Apr 7, 2026
50bface
fix(traces): Remove incorrect retention check from span links
nsdeschenes Apr 7, 2026
6b54030
fix(traces): Remove retention check from trace attribute link in trac…
nsdeschenes Apr 7, 2026
047dda4
ref(traces): Deduplicate minimap JSX in event comparison display
nsdeschenes Apr 7, 2026
f135725
ref(traces): Move DisabledTraceLink to components/explore
nsdeschenes Apr 7, 2026
f28fcb4
chore(explore): Add CODEOWNERS entry for components/explore
nsdeschenes Apr 7, 2026
e29aa02
fix(traces): Preserve issue event links for old trace data
nsdeschenes Apr 7, 2026
1c0e26c
fix(traces): Add tooltip to disabled trace link in agents drawer
nsdeschenes Apr 7, 2026
1814fe4
ref(traces): Remove dead re-export and fix import paths
nsdeschenes Apr 7, 2026
16cd1bf
fix(traces): Add tooltips to disabled trace links missing explanations
nsdeschenes Apr 7, 2026
842ae0e
ref(traces): Migrate simple disabled trace links to DisabledTraceLink
nsdeschenes Apr 7, 2026
ef0d6e0
ref(traces): Migrate complex disabled trace links to DisabledTraceLink
nsdeschenes Apr 7, 2026
60ec9a6
test(traces): Update fieldRenderer tests for DisabledTraceLink
nsdeschenes Apr 7, 2026
bf19f91
fix(traces): Only disable old trace links for event ID column
nsdeschenes Apr 8, 2026
a1c9caa
fix(traces): Fix minor bugs in disabled trace link rollout
nsdeschenes Apr 8, 2026
9c8af0a
fix(traces): Disable trace column links for data older than 30 days
nsdeschenes Apr 8, 2026
91414ce
ref(traces): Scope traceViewLink to the id branch in web vitals table
nsdeschenes Apr 8, 2026
3354073
fix(traces): Close old trace link regressions
nsdeschenes Apr 8, 2026
cb188ef
fix(traces): Preserve truncation for disabled web vitals links
nsdeschenes Apr 8, 2026
e908096
ref(traces): Block old trace redirects in event detail pages
nsdeschenes Apr 8, 2026
6241dde
ref(traces): Disable trace preview and waterfall overlay for old events
nsdeschenes Apr 8, 2026
4633ead
ref(traces): Disable links in trace drawer for old trace data
nsdeschenes Apr 8, 2026
266e515
ref(traces): Disable trace links in transaction tables for old data
nsdeschenes Apr 8, 2026
168f928
fix(traces): Use >= for 30-day boundary and improve test coverage
nsdeschenes Apr 8, 2026
820202f
fix(traces): Normalize query timestamp to string for type safety
nsdeschenes Apr 8, 2026
21be9ca
fix(traces): Hoist isOld check out of flatMap and document normalizeT…
nsdeschenes Apr 8, 2026
2bdabe5
fix(traces): Add missing disabled tooltips and simplify old-data checks
nsdeschenes Apr 8, 2026
4390ae4
fix(traces): Skip old-trace error when redirecting to issue events
nsdeschenes Apr 8, 2026
a236ec1
fix(profiling): Disable trace link for old data in profile events table
nsdeschenes Apr 8, 2026
d167f9e
fix(web-vitals): Show trace ID consistently in disabled and linked st…
nsdeschenes Apr 8, 2026
2b65aac
Tidy up things
nsdeschenes Apr 8, 2026
fe77b0a
Create tooltip wrapper component
nsdeschenes Apr 8, 2026
354cc68
Use new tooltip helper component
nsdeschenes Apr 8, 2026
fc10990
test(performance): Fix spanEvidenceKeyValueList tests after trace lin…
nsdeschenes Apr 8, 2026
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ tests/sentry/api/endpoints/test_organization_attribute_mappings.py @get
/static/app/views/explore/logs/logsTabSeerComboBox.tsx @getsentry/explore @getsentry/machine-learning-ai
/static/app/views/explore/spans/spansTabSeerComboBox.tsx @getsentry/explore @getsentry/machine-learning-ai
/static/app/views/traces/ @getsentry/explore
/static/app/components/explore/ @getsentry/explore
/static/app/components/quickTrace/ @getsentry/explore
/static/app/components/dnd/ @getsentry/explore
/src/sentry/insights/ @getsentry/data-browsing
Expand Down
9 changes: 9 additions & 0 deletions static/app/components/discover/transactionsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {Location, LocationDescriptor} from 'history';
import {LinkButton} from '@sentry/scraps/button';
import {Link} from '@sentry/scraps/link';

import {DisabledTraceLink} from 'sentry/components/explore/disabledTraceLink';
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
import {PanelTable} from 'sentry/components/panels/panelTable';
import {QuestionTooltip} from 'sentry/components/questionTooltip';
Expand All @@ -21,6 +22,7 @@ import {fieldAlignment, getAggregateAlias} from 'sentry/utils/discover/fields';
import {ViewReplayLink} from 'sentry/utils/discover/viewReplayLink';
import {isEmptyObject} from 'sentry/utils/object/isEmptyObject';
import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
import {isPartialSpanOrTraceData} from 'sentry/utils/trace/isOlderThan30Days';
import type {Actions} from 'sentry/views/discover/table/cellAction';
import {CellAction} from 'sentry/views/discover/table/cellAction';
import type {TableColumn} from 'sentry/views/discover/table/types';
Expand Down Expand Up @@ -147,6 +149,11 @@ export function TransactionsTable(props: Props) {
let rendered = fieldRenderer(row, {organization, location, theme});

const target = generateLink?.[field]?.(organization, row, location);
const isOldTraceTarget =
['id', 'trace'].includes(field) &&
!!row.trace &&
!!row.timestamp &&
isPartialSpanOrTraceData(row.timestamp);

if (fields[index] === 'profile.id') {
rendered = (
Expand All @@ -160,6 +167,8 @@ export function TransactionsTable(props: Props) {
<IconProfiling size="xs" />
</LinkButton>
);
} else if (isOldTraceTarget) {
rendered = <DisabledTraceLink type="trace">{rendered}</DisabledTraceLink>;
} else if (target && !isEmptyObject(target)) {
if (fields[index] === 'replayId') {
rendered = (
Expand Down
11 changes: 11 additions & 0 deletions static/app/components/events/contexts/knownContext/trace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import type {Location} from 'history';
import {Tooltip} from '@sentry/scraps/tooltip';

import {getContextKeys} from 'sentry/components/events/contexts/utils';
import {getEventTimestampInSeconds} from 'sentry/components/events/interfaces/utils';
import {DisabledTraceLink} from 'sentry/components/explore/disabledTraceLink';
import {generateTraceTarget} from 'sentry/components/quickTrace/utils';
import {t} from 'sentry/locale';
import type {Event} from 'sentry/types/event';
import type {KeyValueListData} from 'sentry/types/group';
import type {Organization} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {isPartialSpanOrTraceData} from 'sentry/utils/trace/isOlderThan30Days';
import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils';

enum TraceContextKeys {
Expand Down Expand Up @@ -66,6 +69,14 @@ export function getTraceContextData({
const traceWasSampled = data?.sampled ?? true;

if (traceWasSampled) {
if (isPartialSpanOrTraceData(getEventTimestampInSeconds(event))) {
return {
key: ctxKey,
subject: t('Trace ID'),
value: <DisabledTraceLink type="trace">{traceId}</DisabledTraceLink>,
};
}

const link = generateTraceTarget(event, organization, location);
const hasPerformanceView = organization.features.includes('performance-view');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {DiscoverDatasets} from 'sentry/utils/discover/types';
import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
import {getShortEventId} from 'sentry/utils/events';
import {useApiQuery} from 'sentry/utils/queryClient';
import {isPartialSpanOrTraceData} from 'sentry/utils/trace/isOlderThan30Days';
import {useLocation} from 'sentry/utils/useLocation';
import {useOrganization} from 'sentry/utils/useOrganization';

Expand Down Expand Up @@ -215,6 +216,34 @@ function EventDisplay({
location,
organization,
});

const minimapContent = (
<MinimapContainer>
<MinimapPositioningContainer>
<ActualMinimap
theme={theme}
spans={waterfallModel.getWaterfall({
viewStart: 0,
viewEnd: 1,
})}
generateBounds={waterfallModel.generateBounds({
viewStart: 0,
viewEnd: 1,
})}
dividerPosition={0}
rootSpan={waterfallModel.rootSpan.span}
/>
</MinimapPositioningContainer>
</MinimapContainer>
);

const isOld = isPartialSpanOrTraceData(eventData.endTimestamp);
const minimap = isOld ? (
minimapContent
) : (
<Link to={fullEventTarget}>{minimapContent}</Link>
);

return (
<Stack gap="md">
<div>
Expand Down Expand Up @@ -245,13 +274,19 @@ function EventDisplay({
</OverlayTrigger.Button>
)}
/>
<LinkButton
tooltipProps={{title: t('Full Event Details')}}
size={BUTTON_SIZE}
to={fullEventTarget}
aria-label={t('Full Event Details')}
icon={<IconOpen />}
/>
<Tooltip
title={t('Trace data is only available for the last 30 days')}
disabled={!isOld}
>
<LinkButton
tooltipProps={{title: isOld ? undefined : t('Full Event Details')}}
size={BUTTON_SIZE}
to={fullEventTarget}
disabled={isOld}
aria-label={t('Full Event Details')}
icon={<IconOpen />}
/>
</Tooltip>
</StyledEventControls>
<div>
<NavButtons>
Expand Down Expand Up @@ -279,25 +314,7 @@ function EventDisplay({
</div>
</Flex>
<ComparisonContentWrapper>
<Link to={fullEventTarget}>
<MinimapContainer>
<MinimapPositioningContainer>
<ActualMinimap
theme={theme}
spans={waterfallModel.getWaterfall({
viewStart: 0,
viewEnd: 1,
})}
generateBounds={waterfallModel.generateBounds({
viewStart: 0,
viewEnd: 1,
})}
dividerPosition={0}
rootSpan={waterfallModel.rootSpan.span}
/>
</MinimapPositioningContainer>
</MinimapContainer>
</Link>
{minimap}

<OpsBreakdown event={eventData} operationNameFilters={noFilter} hideHeader />
</ComparisonContentWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {EventFixture} from 'sentry-fixture/event';
import {GroupFixture} from 'sentry-fixture/group';
import {OrganizationFixture} from 'sentry-fixture/organization';

import {initializeData} from 'sentry-test/performance/initializePerformanceData';
import {render, screen} from 'sentry-test/reactTestingLibrary';
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
import {resetMockDate, setMockDate} from 'sentry-test/utils';

import {EntryType} from 'sentry/types/event';
import type {TraceEventResponse} from 'sentry/views/issueDetails/traceTimeline/useTraceTimelineEvents';
Expand Down Expand Up @@ -39,6 +41,10 @@ describe('EventTraceView', () => {
});
});

afterEach(() => {
resetMockDate();
});

it('renders a trace', async () => {
const size = 20;
MockApiClient.addMockResponse({
Expand Down Expand Up @@ -146,4 +152,27 @@ describe('EventTraceView', () => {

expect(await screen.findByText('Trace Preview')).toBeInTheDocument();
});

it('disables the trace preview button when the trace is older than 30 days', async () => {
setMockDate(new Date('2025-10-06T00:00:00').getTime());

render(
<EventTraceView
group={group}
event={EventFixture({
...event,
dateCreated: '2025-08-01T00:00:00Z',
})}
organization={OrganizationFixture({features: []})}
/>
);

const button = screen.getByRole('button', {name: 'View Full Trace'});
expect(button).toHaveAttribute('aria-disabled', 'true');

await userEvent.hover(button);
expect(
await screen.findByText('Trace data is only available for the last 30 days')
).toBeInTheDocument();
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.

Test expects wrong tooltip text from DisabledTraceLinkTooltip

Medium Severity

The tests assert the tooltip text is "Trace data is only available for the last 30 days", but the DisabledTraceLinkTooltip component actually renders "Trace is older than 30 days" (from disabledTraceLink.tsx line 35). The component wrapping the LinkButton in eventTraceView.tsx and profileEventEvidence.tsx uses DisabledTraceLinkTooltip, so these tests will fail due to the string mismatch.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fc10990. Configure here.

});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
TRACE_WATERFALL_PREFERENCES_KEY,
} from 'sentry/components/events/interfaces/performance/utils';
import {getEventTimestampInSeconds} from 'sentry/components/events/interfaces/utils';
import {DisabledTraceLinkTooltip} from 'sentry/components/explore/disabledTraceLink';
import {generateTraceTarget} from 'sentry/components/quickTrace/utils';
import {t} from 'sentry/locale';
import {type Event} from 'sentry/types/event';
import {type Group} from 'sentry/types/group';
import type {Organization} from 'sentry/types/organization';
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
import {useRouteAnalyticsParams} from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
import {isPartialSpanOrTraceData} from 'sentry/utils/trace/isOlderThan30Days';
import {useLocation} from 'sentry/utils/useLocation';
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
Expand Down Expand Up @@ -175,21 +177,25 @@ export function EventTraceView({group, event, organization}: EventTraceViewProps
);

const hasTracePreviewFeature = organization.features.includes('profiling');
const isOld = isPartialSpanOrTraceData(getEventTimestampInSeconds(event));

return (
<InterimSection
type={SectionKey.TRACE}
title={t('Trace Preview')}
actions={
<Grid flow="column" align="center" gap="md">
<LinkButton
size="xs"
to={getTraceLinkForIssue(traceTarget)}
analyticsEventName="Issue Details: View Full Trace Action Button Clicked"
analyticsEventKey="issue_details.view_full_trace_action_button_clicked"
>
{t('View Full Trace')}
</LinkButton>
<DisabledTraceLinkTooltip disabled={!isOld} type="trace">
<LinkButton
size="xs"
to={getTraceLinkForIssue(traceTarget)}
analyticsEventName="Issue Details: View Full Trace Action Button Clicked"
analyticsEventKey="issue_details.view_full_trace_action_button_clicked"
disabled={isOld}
>
{t('View Full Trace')}
</LinkButton>
</DisabledTraceLinkTooltip>
</Grid>
}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import {
getSpanSubTimings,
SpanSubTimingName,
} from 'sentry/components/events/interfaces/spans/utils';
import {getEventTimestampInSeconds} from 'sentry/components/events/interfaces/utils';
import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
import {DisabledTraceLinkTooltip} from 'sentry/components/explore/disabledTraceLink';
import {t} from 'sentry/locale';
import type {Entry, EntryRequest, Event, EventTransaction} from 'sentry/types/event';
import {EntryType} from 'sentry/types/event';
Expand All @@ -40,6 +42,7 @@ import {formatBytesBase2} from 'sentry/utils/bytes/formatBytesBase2';
import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
import {toRoundedPercent} from 'sentry/utils/number/toRoundedPercent';
import {SQLishFormatter} from 'sentry/utils/sqlish';
import {isPartialSpanOrTraceData} from 'sentry/utils/trace/isOlderThan30Days';
import {safeURL} from 'sentry/utils/url/safeURL';
import {useLocation} from 'sentry/utils/useLocation';
import {useOrganization} from 'sentry/utils/useOrganization';
Expand Down Expand Up @@ -374,10 +377,13 @@ function AIDetectedSpanEvidence({
organization,
});

const isOld = isPartialSpanOrTraceData(getEventTimestampInSeconds(event));
const actionButton = projectSlug ? (
<LinkButton size="xs" to={eventDetailsLocation}>
{t('View Full Trace')}
</LinkButton>
<DisabledTraceLinkTooltip disabled={!isOld} type="trace">
<LinkButton size="xs" to={eventDetailsLocation} disabled={isOld}>
{t('View Full Trace')}
</LinkButton>
</DisabledTraceLinkTooltip>
) : undefined;

const transactionRow = makeRow(
Expand Down Expand Up @@ -616,10 +622,13 @@ const makeTransactionNameRow = (
organization,
});

const isOld = isPartialSpanOrTraceData(getEventTimestampInSeconds(event));
const actionButton = projectSlug ? (
<LinkButton size="xs" to={eventDetailsLocation}>
{t('View Full Trace')}
</LinkButton>
<DisabledTraceLinkTooltip disabled={!isOld} type="trace">
<LinkButton size="xs" to={eventDetailsLocation} disabled={isOld}>
{t('View Full Trace')}
</LinkButton>
</DisabledTraceLinkTooltip>
) : undefined;

return makeRow(
Expand Down
4 changes: 3 additions & 1 deletion static/app/components/events/interfaces/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,9 @@ const timestampsFieldCandidates = [
'endTimestamp',
];

export function getEventTimestampInSeconds(event: Event): number | undefined {
export function getEventTimestampInSeconds(event?: Event): number | undefined {
if (!event) return undefined;

for (const key of timestampsFieldCandidates) {
if (key in event) {
const value = event[key as keyof Event];
Expand Down
Loading
Loading