Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(issues): Add overlays to each trace row #87371

Merged
merged 3 commits into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import {Fragment, useMemo} from 'react';
import styled from '@emotion/styled';
import type {LocationDescriptor} from 'history';

import ButtonBar from 'sentry/components/buttonBar';
import {LinkButton} from 'sentry/components/core/button';
import Link from 'sentry/components/links/link';
import {generateTraceTarget} from 'sentry/components/quickTrace/utils';
import {t} from 'sentry/locale';
import type {Event} from 'sentry/types/event';
import {type Group, IssueCategory} from 'sentry/types/group';
import type {Organization} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {trackAnalytics} from 'sentry/utils/analytics';
import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
import {useLocation} from 'sentry/utils/useLocation';
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
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 {getTraceLinkForIssue} from 'sentry/views/performance/newTraceDetails/issuesTraceWaterfallOverlay';
import {useIssuesTraceTree} from 'sentry/views/performance/newTraceDetails/traceApi/useIssuesTraceTree';
import {useTrace} from 'sentry/views/performance/newTraceDetails/traceApi/useTrace';
import {useTraceMeta} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceMeta';
Expand Down Expand Up @@ -59,15 +56,9 @@ interface EventTraceViewInnerProps {
event: Event;
organization: Organization;
traceId: string;
traceTarget: LocationDescriptor;
}

function EventTraceViewInner({
event,
organization,
traceId,
traceTarget,
}: EventTraceViewInnerProps) {
function EventTraceViewInner({event, organization, traceId}: EventTraceViewInnerProps) {
const timestamp = new Date(event.dateReceived).getTime() / 1e3;

const trace = useTrace({
Expand Down Expand Up @@ -115,34 +106,11 @@ function EventTraceViewInner({
replay={null}
event={event}
/>
<IssuesTraceOverlayContainer
to={getHrefFromTraceTarget(traceTarget)}
onClick={() => {
trackAnalytics('issue_details.view_full_trace_waterfall_clicked', {
organization,
});
}}
/>
</IssuesTraceContainer>
</TraceStateProvider>
);
}

function getHrefFromTraceTarget(traceTarget: LocationDescriptor) {
if (typeof traceTarget === 'string') {
return traceTarget;
}

const searchParams = new URLSearchParams();
for (const key in traceTarget.query) {
if (defined(traceTarget.query[key])) {
searchParams.append(key, traceTarget.query[key]);
}
}

return `${traceTarget.pathname}?${searchParams.toString()}`;
}

function OneOtherIssueEvent({event}: {event: Event}) {
const {isLoading, oneOtherIssueEvent} = useTraceTimelineEvents({event});
useRouteAnalyticsParams(oneOtherIssueEvent ? {has_related_trace_issue: true} : {});
Expand All @@ -163,12 +131,6 @@ const IssuesTraceContainer = styled('div')`
position: relative;
`;

const IssuesTraceOverlayContainer = styled(Link)`
position: absolute;
inset: 0;
z-index: 10;
`;

interface EventTraceViewProps {
event: Event;
group: Group;
Expand Down Expand Up @@ -216,7 +178,7 @@ export function EventTraceView({group, event, organization}: EventTraceViewProps
<SwitchToNonEAPTraceButton location={location} organization={organization} />
<LinkButton
size="xs"
to={getHrefFromTraceTarget(traceTarget)}
to={getTraceLinkForIssue(traceTarget)}
analyticsEventName="Issue Details: View Full Trace Action Button Clicked"
analyticsEventKey="issue_details.view_full_trace_action_button_clicked"
>
Expand All @@ -231,7 +193,6 @@ export function EventTraceView({group, event, organization}: EventTraceViewProps
event={event}
organization={organization}
traceId={traceId}
traceTarget={traceTarget}
/>
)}
</InterimSection>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import {lazy, Suspense, useMemo} from 'react';
import styled from '@emotion/styled';
import type {LocationDescriptor} from 'history';

import Link from 'sentry/components/links/link';
import {generateTraceTarget} from 'sentry/components/quickTrace/utils';
import type {Event} from 'sentry/types/event';
import type {Organization} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {trackAnalytics} from 'sentry/utils/analytics';
import {useLocation} from 'sentry/utils/useLocation';
import {useIssuesTraceTree} from 'sentry/views/performance/newTraceDetails/traceApi/useIssuesTraceTree';
import {useTrace} from 'sentry/views/performance/newTraceDetails/traceApi/useTrace';
import {useTraceMeta} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceMeta';
import {useTraceRootEvent} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceRootEvent';
import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs';
import {
loadTraceViewPreferences,
type TracePreferencesState,
Expand All @@ -28,21 +21,6 @@ const LazyIssuesTraceWaterfall = lazy(() =>
)
);

function getHrefFromTraceTarget(traceTarget: LocationDescriptor) {
if (typeof traceTarget === 'string') {
return traceTarget;
}

const searchParams = new URLSearchParams();
for (const key in traceTarget.query) {
if (defined(traceTarget.query[key])) {
searchParams.append(key, traceTarget.query[key]);
}
}

return `${traceTarget.pathname}?${searchParams.toString()}`;
}

const DEFAULT_ISSUE_DETAILS_TRACE_VIEW_PREFERENCES: TracePreferencesState = {
drawer: {
minimized: true,
Expand Down Expand Up @@ -78,7 +56,6 @@ export function SpanEvidenceTraceView({
}: SpanEvidenceTraceViewProps) {
const timestamp = new Date(event.dateReceived).getTime() / 1e3;

const location = useLocation();
const trace = useTrace({
timestamp,
traceSlug: traceId,
Expand All @@ -104,19 +81,6 @@ export function SpanEvidenceTraceView({
return null;
}

const traceTarget = generateTraceTarget(
event,
organization,
{
...location,
query: {
...location.query,
groupId: event.groupID,
},
},
TraceViewSources.ISSUE_DETAILS
);

return (
<TraceStateProvider
initialPreferences={preferences}
Expand All @@ -137,14 +101,6 @@ export function SpanEvidenceTraceView({
event={event}
/>
</Suspense>
<IssuesTraceOverlayContainer
to={getHrefFromTraceTarget(traceTarget)}
onClick={() => {
trackAnalytics('issue_details.view_full_trace_waterfall_clicked', {
organization,
});
}}
/>
</IssuesTraceContainer>
</TraceStateProvider>
);
Expand All @@ -153,9 +109,3 @@ export function SpanEvidenceTraceView({
const IssuesTraceContainer = styled('div')`
position: relative;
`;

const IssuesTraceOverlayContainer = styled(Link)`
position: absolute;
inset: 0;
z-index: 10;
`;
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type React from 'react';
import {
Fragment,
useCallback,
Expand All @@ -16,6 +15,7 @@ import type {Project} from 'sentry/types/project';
import {trackAnalytics} from 'sentry/utils/analytics';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import {IssueTraceWaterfallOverlay} from 'sentry/views/performance/newTraceDetails/issuesTraceWaterfallOverlay';
import {
isSpanNode,
isTraceErrorNode,
Expand Down Expand Up @@ -58,6 +58,7 @@ export function IssuesTraceWaterfall(props: IssuesTraceWaterfallProps) {
const organization = useOrganization();
const traceState = useTraceState();
const traceDispatch = useTraceStateDispatch();
const containerRef = useRef<HTMLDivElement>(null);

const [forceRender, rerender] = useReducer(x => (x + 1) % Number.MAX_SAFE_INTEGER, 0);

Expand Down Expand Up @@ -318,21 +319,32 @@ export function IssuesTraceWaterfall(props: IssuesTraceWaterfallProps) {
: 8
}
>
<IssuesPointerDisabled>
<Trace
metaQueryResults={props.meta}
trace={props.tree}
rerender={rerender}
trace_id={props.traceSlug}
onRowClick={onRowClick}
onTraceSearch={noopTraceSearch}
previouslyFocusedNodeRef={previouslyFocusedNodeRef}
manager={viewManager}
scheduler={traceScheduler}
forceRerender={forceRender}
isLoading={props.tree.type === 'loading' || onLoadScrollStatus === 'pending'}
<IssuesTraceContainer ref={containerRef}>
<IssuesPointerDisabled>
<Trace
metaQueryResults={props.meta}
trace={props.tree}
rerender={rerender}
trace_id={props.traceSlug}
onRowClick={onRowClick}
onTraceSearch={noopTraceSearch}
previouslyFocusedNodeRef={previouslyFocusedNodeRef}
manager={viewManager}
scheduler={traceScheduler}
forceRerender={forceRender}
isLoading={
props.tree.type === 'loading' || onLoadScrollStatus === 'pending'
}
/>
</IssuesPointerDisabled>
<IssueTraceWaterfallOverlay
containerRef={containerRef}
event={props.event}
groupId={props.event.groupID}
tree={props.tree}
viewManager={viewManager}
/>
</IssuesPointerDisabled>
</IssuesTraceContainer>

{props.tree.type === 'loading' || onLoadScrollStatus === 'pending' ? (
<TraceWaterfallState.Loading />
Expand Down Expand Up @@ -370,3 +382,9 @@ const IssuesTraceGrid = styled(TraceGrid)<{
Math.min(Math.max(p.rowCount, MIN_ROW_COUNT), MAX_ROW_COUNT) * ROW_HEIGHT +
HEADER_HEIGHT}px;
`;

const IssuesTraceContainer = styled('div')`
position: relative;
height: 100%;
width: 100%;
`;
Loading
Loading