diff --git a/static/app/components/events/interfaces/performance/eventTraceView.spec.tsx b/static/app/components/events/interfaces/performance/eventTraceView.spec.tsx index a01c8f1e53f503..a14134f75d6d75 100644 --- a/static/app/components/events/interfaces/performance/eventTraceView.spec.tsx +++ b/static/app/components/events/interfaces/performance/eventTraceView.spec.tsx @@ -22,7 +22,7 @@ window.ResizeObserver = ResizeObserver; describe('EventTraceView', () => { const traceId = 'this-is-a-good-trace-id'; const {organization, project} = initializeData({ - features: ['profiling', 'issue-details-always-show-trace'], + features: ['profiling'], }); const group = GroupFixture(); const event = EventFixture({ @@ -103,6 +103,32 @@ describe('EventTraceView', () => { }); it('still renders trace link for performance issues', async () => { + const oneOtherIssueEvent: TraceEventResponse = { + data: [ + { + // In issuePlatform, the message contains the title and the transaction + message: '/api/slow/ Slow DB Query SELECT "sentry_monitorcheckin"."monitor_id"', + timestamp: '2024-01-24T09:09:03+00:00', + 'issue.id': 1000, + project: project.slug, + 'project.name': project.name, + title: 'Slow DB Query', + id: 'abc', + transaction: 'n/a', + culprit: '/api/slow/', + 'event.type': '', + }, + ], + meta: {fields: {}, units: {}}, + }; + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/events/`, + body: oneOtherIssueEvent, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/projects/`, + body: [], + }); const perfGroup = GroupFixture({issueCategory: IssueCategory.PERFORMANCE}); const perfEvent = EventFixture({ occurrence: { @@ -129,6 +155,9 @@ describe('EventTraceView', () => { expect( await screen.findByRole('link', {name: 'View Full Trace'}) ).toBeInTheDocument(); + expect( + screen.getByText('One other issue appears in the same trace.') + ).toBeInTheDocument(); }); it('does not render the trace preview if it has no transactions', async () => { @@ -154,8 +183,5 @@ describe('EventTraceView', () => { render(); expect(await screen.findByText('Trace')).toBeInTheDocument(); - expect( - await screen.findByRole('link', {name: 'View Full Trace'}) - ).toBeInTheDocument(); }); }); diff --git a/static/app/components/events/interfaces/performance/eventTraceView.tsx b/static/app/components/events/interfaces/performance/eventTraceView.tsx index f47817c661bb32..508a4ba2610c2b 100644 --- a/static/app/components/events/interfaces/performance/eventTraceView.tsx +++ b/static/app/components/events/interfaces/performance/eventTraceView.tsx @@ -1,7 +1,8 @@ -import {useMemo} from 'react'; +import {Fragment, useMemo} from 'react'; import styled from '@emotion/styled'; import {LinkButton} from 'sentry/components/button'; +import Link from 'sentry/components/links/link'; import {generateTraceTarget} from 'sentry/components/quickTrace/utils'; import {IconOpen} from 'sentry/icons'; import {t} from 'sentry/locale'; @@ -9,11 +10,13 @@ import {space} from 'sentry/styles/space'; import type {Event} from 'sentry/types/event'; import {type Group, IssueCategory} from 'sentry/types/group'; import type {Organization} from 'sentry/types/organization'; +import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection'; -import {TraceDataSection} from 'sentry/views/issueDetails/traceDataSection'; +import {TraceIssueEvent} from 'sentry/views/issueDetails/traceTimeline/traceIssue'; +import {useTraceTimelineEvents} from 'sentry/views/issueDetails/traceTimeline/useTraceTimelineEvents'; import {IssuesTraceWaterfall} from 'sentry/views/performance/newTraceDetails/issuesTraceWaterfall'; import {useIssuesTraceTree} from 'sentry/views/performance/newTraceDetails/traceApi/useIssuesTraceTree'; import {useTrace} from 'sentry/views/performance/newTraceDetails/traceApi/useTrace'; @@ -128,16 +131,47 @@ function IssuesTraceOverlay({event}: {event: Event}) { return ( - } - aria-label={t('Open Trace')} - to={traceTarget} - /> + } to={traceTarget}> + {t('View Full Trace')} + ); } +function OneOtherIssueEvent({event}: {event: Event}) { + const location = useLocation(); + const organization = useOrganization(); + const {isLoading, oneOtherIssueEvent} = useTraceTimelineEvents({event}); + useRouteAnalyticsParams(oneOtherIssueEvent ? {has_related_trace_issue: true} : {}); + + if (isLoading || !oneOtherIssueEvent) { + return null; + } + + const traceTarget = generateTraceTarget( + event, + organization, + { + ...location, + query: { + ...location.query, + groupId: event.groupID, + }, + }, + TraceViewSources.ISSUE_DETAILS + ); + + return ( + + + {t('One other issue appears in the same trace. ')} + {t('View Full Trace')} + + + + ); +} + const IssuesTraceContainer = styled('div')` position: relative; `; @@ -165,18 +199,14 @@ export function EventTraceView({group, event, organization}: EventTraceViewProps } const hasProfilingFeature = organization.features.includes('profiling'); - const hasIssueDetailsTrace = organization.features.includes( - 'issue-details-always-show-trace' - ); const hasTracePreviewFeature = hasProfilingFeature && - hasIssueDetailsTrace && // Only display this for error or default events since performance events are handled elsewhere group.issueCategory !== IssueCategory.PERFORMANCE; return ( - + {hasTracePreviewFeature && ( = {}; @@ -28,6 +30,11 @@ export function TraceDataSection({event}: {event: Event}) { return null; } + const noEvents = !isLoading && traceEvents.length === 0; + if (hasStreamlinedUI && (!oneOtherIssueEvent || noEvents)) { + return null; + } + return (