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(new-trace): Adding trace header information to drawer. #70717

Merged
merged 5 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
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
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
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
@@ -1,27 +1,43 @@
import {Fragment, useMemo} from 'react';
import styled from '@emotion/styled';

import type {Tag} from 'sentry/actionCreators/events';
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 {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types';
import type {EventTransaction} from 'sentry/types/event';
import {generateQueryWithTag} from 'sentry/utils';
import type EventView from 'sentry/utils/discover/eventView';
import {formatTagKey} from 'sentry/utils/discover/fields';
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 {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {useParams} from 'sentry/utils/useParams';
import {normalizeUrl} from 'sentry/utils/withDomainRequired';
import Tags from 'sentry/views/discover/tags';
import {TraceWarnings} from 'sentry/views/performance/newTraceDetails/traceWarnings';
import type {TraceType} from 'sentry/views/performance/traceDetails/newTraceDetailsContent';
import {SpanTimeRenderer} from 'sentry/views/performance/traces/fieldRenderers';

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

type TraceDetailsProps = {
metaResults: UseApiQueryResult<TraceMeta | null, any>;
node: TraceTreeNode<TraceTree.NodeValue> | null;
rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
tagsQueryResults: UseApiQueryResult<Tag[], RequestError>;
Expand Down Expand Up @@ -56,26 +72,244 @@ export function TraceDetails(props: TraceDetailsProps) {
<Fragment>
{props.tree.type === 'trace' ? <TraceWarnings type={props.traceType} /> : null}
<IssueList issues={issues} node={props.node} organization={organization} />
<GeneralInfo
organization={organization}
traces={props.traces}
tree={props.tree}
node={props.node}
rootEventResults={props.rootEventResults}
metaResults={props.metaResults}
/>
{rootEvent ? (
<Tags
tagsQueryResults={props.tagsQueryResults}
generateUrl={(key: string, value: string) => {
const url = props.traceEventView.getResultsViewUrlTarget(
organization.slug,
false
);
url.query = generateQueryWithTag(url.query, {
key: formatTagKey(key),
value,
});
return url;
}}
totalValues={props.tree.eventsCount}
eventView={props.traceEventView}
organization={organization}
location={location}
/>
<TagsWrapper>
<Tags
tagsQueryResults={props.tagsQueryResults}
generateUrl={(key: string, value: string) => {
const url = props.traceEventView.getResultsViewUrlTarget(
organization.slug,
false
);
url.query = generateQueryWithTag(url.query, {
key: formatTagKey(key),
value,
});
return url;
}}
totalValues={props.tree.eventsCount}
eventView={props.traceEventView}
organization={organization}
location={location}
/>
</TagsWrapper>
) : null}
</Fragment>
);
}

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

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
/>
);
}

const TagsWrapper = styled('div')`
padding-bottom: ${space(1)};
`;
Loading
Loading