Skip to content

Commit

Permalink
feat(new-trace): Adding trace header information to drawer. (#70717)
Browse files Browse the repository at this point in the history
Before:
<img width="1512" alt="Screenshot 2024-05-12 at 11 20 05 PM"
src="https://github.com/getsentry/sentry/assets/60121741/721ed8fd-87db-4e17-9769-1ae5215a1c19">

After:
<img width="1510" alt="Screenshot 2024-05-12 at 11 19 50 PM"
src="https://github.com/getsentry/sentry/assets/60121741/3162dba2-0437-4093-8978-54f265b024f2">

---------

Co-authored-by: Abdullah Khan <[email protected]>
  • Loading branch information
Abdkhan14 and Abdullah Khan authored May 13, 2024
1 parent bef6fe8 commit 4b1e575
Show file tree
Hide file tree
Showing 9 changed files with 450 additions and 51 deletions.
1 change: 1 addition & 0 deletions static/app/components/events/interfaces/spans/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export enum TickAlignment {
}

export type TraceContextType = {
client_sample_rate?: number;
count?: number;
description?: string;
exclusive_time?: number;
Expand Down
2 changes: 1 addition & 1 deletion static/app/utils/queryClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ export function fetchInfiniteQuery<TResponseData>(api: Client) {
function parsePageParam(dir: 'previous' | 'next') {
return ([, , resp]: ApiResult<unknown>) => {
const parsed = parseLinkHeader(resp?.getResponseHeader('Link') ?? null);
return parsed[dir].results ? parsed[dir] : null;
return parsed[dir]?.results ? parsed[dir] : null;
};
}

Expand Down
16 changes: 4 additions & 12 deletions static/app/views/performance/newTraceDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ import {TraceSearchInput} from './traceSearch/traceSearchInput';
import {searchInTraceTree} from './traceState/traceSearch';
import {isTraceNode} from './guards';
import {Trace} from './trace';
import {TraceHeader} from './traceHeader';
import {TraceMetadataHeader} from './traceMetadataHeader';
import {TraceReducer, type TraceReducerState} from './traceState';
import {TraceUXChangeAlert} from './traceUXChangeBanner';
Expand Down Expand Up @@ -802,14 +801,6 @@ function TraceViewContent(props: TraceViewContentProps) {
traceSlug={props.traceSlug}
traceEventView={props.traceEventView}
/>
<TraceHeader
tree={tree}
rootEventResults={rootEvent}
metaResults={props.metaResults}
organization={props.organization}
traces={props.trace}
traceID={props.traceSlug}
/>
<TraceInnerLayout>
<TraceToolbar>
<TraceSearchInput
Expand Down Expand Up @@ -845,6 +836,7 @@ function TraceViewContent(props: TraceViewContentProps) {
) : null}

<TraceDrawer
metaResults={props.metaResults}
traceType={shape}
trace={tree}
traceGridRef={traceGridRef}
Expand Down Expand Up @@ -893,7 +885,8 @@ const TraceInnerLayout = styled('div')`
display: flex;
flex-direction: column;
flex: 1 1 100%;
padding: 0 ${space(2)} 0 ${space(2)};
padding: ${space(2)};
background-color: ${p => p.theme.background};
--info: ${p => p.theme.purple400};
Expand All @@ -920,8 +913,7 @@ const TraceGrid = styled('div')<{
box-shadow: 0 0 0 1px ${p => p.theme.border};
flex: 1 1 100%;
display: grid;
border-top-left-radius: ${p => p.theme.borderRadius};
border-top-right-radius: ${p => p.theme.borderRadius};
border-radius: ${p => p.theme.borderRadius};
overflow: hidden;
position: relative;
/* false positive for grid layout */
Expand Down
4 changes: 2 additions & 2 deletions static/app/views/performance/newTraceDetails/trace.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function mockTraceTagsResponse(resp?: Partial<ResponseType>) {
url: '/organizations/org-slug/events-facets/',
method: 'GET',
asyncDelay: 1,
...(resp ?? {}),
...(resp ?? []),
});
}

Expand Down Expand Up @@ -523,7 +523,7 @@ describe('trace view', () => {
},
});
mockTraceMetaResponse();
mockTraceTagsResponse({});
mockTraceTagsResponse();

render(<TraceViewWithProviders traceSlug="trace-id" />);
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ function SectionCard({
title: React.ReactNode;
disableTruncate?: boolean;
}) {
const [showingAll, setShowingAll] = useState(disableTruncate ?? false);
const [showingAll, setShowingAll] = useState(false);
const renderText = showingAll ? t('Show less') : t('Show more') + '...';

if (items.length === 0) {
Expand All @@ -606,7 +606,7 @@ function SectionCard({
return (
<Card>
<CardContentTitle>{title}</CardContentTitle>
{items.slice(0, showingAll ? items.length : 5).map(item => (
{items.slice(0, showingAll || disableTruncate ? items.length : 5).map(item => (
<SectionCardContent key={`context-card-${item.key}`} meta={{}} item={item} />
))}
{items.length > 5 && !disableTruncate ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import {Fragment, useMemo} from 'react';

import Link from 'sentry/components/links/link';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {Tooltip} from 'sentry/components/tooltip';
import {t, tn} from 'sentry/locale';
import type {Organization} from 'sentry/types';
import type {EventTransaction} from 'sentry/types/event';
import getDuration from 'sentry/utils/duration/getDuration';
import {getShortEventId} from 'sentry/utils/events';
import type {
TraceErrorOrIssue,
TraceFullDetailed,
TraceMeta,
TraceSplitResults,
} from 'sentry/utils/performance/quickTrace/types';
import type {UseApiQueryResult} from 'sentry/utils/queryClient';
import type RequestError from 'sentry/utils/requestError/requestError';
import {useParams} from 'sentry/utils/useParams';
import {normalizeUrl} from 'sentry/utils/withDomainRequired';
import {SpanTimeRenderer} from 'sentry/views/performance/traces/fieldRenderers';

import {isTraceNode} from '../../../guards';
import type {TraceTree, TraceTreeNode} from '../../../traceModels/traceTree';
import {type SectionCardKeyValueList, TraceDrawerComponents} from '../../details/styles';

type GeneralInfoProps = {
metaResults: UseApiQueryResult<TraceMeta | null, any>;
node: TraceTreeNode<TraceTree.NodeValue> | null;
organization: Organization;
rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
traces: TraceSplitResults<TraceFullDetailed> | null;
tree: TraceTree;
};

export function GeneralInfo(props: GeneralInfoProps) {
const params = useParams<{traceSlug?: string}>();

const traceNode = props.tree.root.children[0];

const uniqueErrorIssues = useMemo(() => {
if (!traceNode) {
return [];
}

const unique: TraceErrorOrIssue[] = [];

const seenIssues: Set<number> = new Set();

for (const issue of traceNode.errors) {
if (seenIssues.has(issue.issue_id)) {
continue;
}
seenIssues.add(issue.issue_id);
unique.push(issue);
}

return unique;
}, [traceNode]);

const uniquePerformanceIssues = useMemo(() => {
if (!traceNode) {
return [];
}

const unique: TraceErrorOrIssue[] = [];
const seenIssues: Set<number> = new Set();

for (const issue of traceNode.performance_issues) {
if (seenIssues.has(issue.issue_id)) {
continue;
}
seenIssues.add(issue.issue_id);
unique.push(issue);
}

return unique;
}, [traceNode]);

const uniqueIssuesCount = uniqueErrorIssues.length + uniquePerformanceIssues.length;

const traceSlug = useMemo(() => {
return params.traceSlug?.trim() ?? '';
}, [params.traceSlug]);

const isLoading = useMemo(() => {
return (
props.metaResults.isLoading ||
(props.rootEventResults.isLoading && props.rootEventResults.fetchStatus !== 'idle')
);
}, [
props.metaResults.isLoading,
props.rootEventResults.isLoading,
props.rootEventResults.fetchStatus,
]);

if (isLoading) {
return (
<TraceDrawerComponents.SectionCard
items={[
{
key: 'trace_general_loading',
subject: null,
value: <LoadingIndicator size={30} />,
},
]}
title={t('General')}
/>
);
}

if (!(traceNode && isTraceNode(traceNode))) {
throw new Error('Expected a trace node');
}

if (
props.traces?.transactions.length === 0 &&
props.traces.orphan_errors.length === 0
) {
return null;
}

const replay_id = props.rootEventResults?.data?.contexts?.replay?.replay_id;
const browser = props.rootEventResults?.data?.contexts?.browser;

const items: SectionCardKeyValueList = [
{
key: 'trace_id',
subject: t('Trace ID'),
value: <TraceDrawerComponents.CardValueWithCopy value={traceSlug} />,
},
{
key: 'events',
subject: t('Events'),
value: props.metaResults.data
? props.metaResults.data.transactions + props.metaResults.data.errors
: '\u2014',
},
{
key: 'issues',
subject: t('Issues'),
value: (
<Tooltip
title={
uniqueIssuesCount > 0 ? (
<Fragment>
<div>
{tn('%s error issue', '%s error issues', uniqueErrorIssues.length)}
</div>
<div>
{tn(
'%s performance issue',
'%s performance issues',
uniquePerformanceIssues.length
)}
</div>
</Fragment>
) : null
}
showUnderline
position="bottom"
>
{uniqueIssuesCount > 0 ? (
<TraceDrawerComponents.IssuesLink>
{uniqueIssuesCount}
</TraceDrawerComponents.IssuesLink>
) : uniqueIssuesCount === 0 ? (
0
) : (
'\u2014'
)}
</Tooltip>
),
},
{
key: 'start_timestamp',
subject: t('Start Timestamp'),
value: traceNode.space?.[1] ? (
<SpanTimeRenderer timestamp={traceNode.space?.[0]} tooltipShowSeconds />
) : (
'\u2014'
),
},
{
key: 'total_duration',
subject: t('Total Duration'),
value: traceNode.space?.[1]
? getDuration(traceNode.space[1] / 1000, 2, true)
: '\u2014',
},
{
key: 'user',
subject: t('User'),
value:
props.rootEventResults?.data?.user?.email ??
props.rootEventResults?.data?.user?.name ??
'\u2014',
},
{
key: 'browser',
subject: t('Browser'),
value: browser ? browser.name + ' ' + browser.version : '\u2014',
},
];

if (replay_id) {
items.push({
key: 'replay_id',
subject: t('Replay ID'),
value: (
<Link
to={normalizeUrl(
`/organizations/${props.organization.slug}/replays/${replay_id}/`
)}
>
{getShortEventId(replay_id)}
</Link>
),
});
}

return (
<TraceDrawerComponents.SectionCard
items={items}
title={t('General')}
disableTruncate
/>
);
}
Loading

0 comments on commit 4b1e575

Please sign in to comment.