123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- import {Fragment} from 'react';
- import styled from '@emotion/styled';
- import GuideAnchor from 'sentry/components/assistant/guideAnchor';
- import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton';
- import Link from 'sentry/components/links/link';
- import LoadingIndicator from 'sentry/components/loadingIndicator';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconPlay} from 'sentry/icons';
- import {t, tn} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {EventTransaction, Organization} from 'sentry/types';
- import {getShortEventId} from 'sentry/utils/events';
- import {getDuration} from 'sentry/utils/formatters';
- import type {
- 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 {normalizeUrl} from 'sentry/utils/withDomainRequired';
- import {BrowserDisplay} from '../transactionDetails/eventMetas';
- import {MetaData} from '../transactionDetails/styles';
- import {isTraceNode} from './guards';
- import type {TraceTree} from './traceTree';
- function TraceHeaderEmptyTrace() {
- return (
- <TraceHeaderContainer>
- <TraceHeaderRow textAlign="left">
- <MetaData
- headingText={t('User')}
- tooltipText=""
- bodyText={'\u2014'}
- subtext={null}
- />
- <MetaData
- headingText={t('Browser')}
- tooltipText=""
- bodyText={'\u2014'}
- subtext={null}
- />
- </TraceHeaderRow>
- <TraceHeaderRow textAlign="right">
- <GuideAnchor target="trace_view_guide_breakdown">
- <MetaData
- headingText={t('Events')}
- tooltipText=""
- bodyText={'\u2014'}
- subtext={null}
- />
- </GuideAnchor>
- <MetaData
- headingText={t('Issues')}
- tooltipText=""
- bodyText={'\u2014'}
- subtext={null}
- />
- <MetaData
- headingText={t('Total Duration')}
- tooltipText=""
- bodyText={'\u2014'}
- subtext={null}
- />
- </TraceHeaderRow>
- </TraceHeaderContainer>
- );
- }
- type TraceHeaderProps = {
- metaResults: UseApiQueryResult<TraceMeta | null, any>;
- organization: Organization;
- rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
- traceID: string | undefined;
- traces: TraceSplitResults<TraceFullDetailed> | null;
- tree: TraceTree;
- };
- export function TraceHeader({
- metaResults,
- rootEventResults,
- traces,
- organization,
- tree,
- traceID,
- }: TraceHeaderProps) {
- if (traces?.transactions.length === 0 && traces.orphan_errors.length === 0) {
- return <TraceHeaderEmptyTrace />;
- }
- const traceNode = tree.root.children[0];
- if (!(traceNode && isTraceNode(traceNode))) {
- throw new Error('Expected a trace node');
- }
- const errors = traceNode.errors.size || metaResults.data?.errors || 0;
- const performanceIssues =
- traceNode.performance_issues.size || metaResults.data?.performance_issues || 0;
- const errorsAndIssuesCount = errors + performanceIssues;
- const replay_id = rootEventResults?.data?.contexts.replay?.replay_id;
- const showLoadingIndicator =
- (rootEventResults.isLoading && rootEventResults.fetchStatus !== 'idle') ||
- metaResults.isLoading;
- return (
- <TraceHeaderContainer>
- <TraceHeaderRow textAlign="left">
- <MetaData
- headingText={t('User')}
- tooltipText=""
- bodyText={
- showLoadingIndicator ? (
- <LoadingIndicator size={20} mini />
- ) : (
- rootEventResults?.data?.user?.email ??
- rootEventResults?.data?.user?.name ??
- '\u2014'
- )
- }
- subtext={null}
- />
- <MetaData
- headingText={t('Browser')}
- tooltipText=""
- bodyText={
- showLoadingIndicator ? (
- <LoadingIndicator size={20} mini />
- ) : rootEventResults?.data ? (
- <BrowserDisplay event={rootEventResults?.data} showVersion />
- ) : (
- '\u2014'
- )
- }
- subtext={null}
- />
- <MetaData
- headingText={t('Trace')}
- tooltipText=""
- bodyText={
- showLoadingIndicator ? (
- <LoadingIndicator size={20} mini />
- ) : traceID ? (
- <Fragment>
- {getShortEventId(traceID)}
- <CopyToClipboardButton
- borderless
- size="zero"
- iconSize="xs"
- text={traceID}
- />
- </Fragment>
- ) : (
- '\u2014'
- )
- }
- subtext={null}
- />
- {replay_id && (
- <MetaData
- headingText={t('Replay')}
- tooltipText=""
- bodyText={
- <Link
- to={normalizeUrl(
- `/organizations/${organization.slug}/replays/${replay_id}/`
- )}
- >
- <ReplayLinkBody>
- {getShortEventId(replay_id)}
- <IconPlay size="xs" />
- </ReplayLinkBody>
- </Link>
- }
- subtext={null}
- />
- )}
- </TraceHeaderRow>
- <TraceHeaderRow textAlign="right">
- <GuideAnchor target="trace_view_guide_breakdown">
- <MetaData
- headingText={t('Events')}
- tooltipText=""
- bodyText={
- metaResults.isLoading ? (
- <LoadingIndicator size={20} mini />
- ) : metaResults.data ? (
- metaResults.data.transactions + metaResults.data.errors
- ) : (
- '\u2014'
- )
- }
- subtext={null}
- />
- </GuideAnchor>
- <MetaData
- headingText={t('Issues')}
- tooltipText=""
- bodyText={
- <Tooltip
- title={
- errorsAndIssuesCount > 0 ? (
- <Fragment>
- <div>{tn('%s error issue', '%s error issues', errors)}</div>
- <div>
- {tn(
- '%s performance issue',
- '%s performance issues',
- performanceIssues
- )}
- </div>
- </Fragment>
- ) : null
- }
- showUnderline
- position="bottom"
- >
- {metaResults.isLoading ? (
- <LoadingIndicator size={20} mini />
- ) : errorsAndIssuesCount >= 0 ? (
- errorsAndIssuesCount
- ) : (
- '\u2014'
- )}
- </Tooltip>
- }
- subtext={null}
- />
- <MetaData
- headingText={t('Total Duration')}
- tooltipText=""
- bodyText={
- metaResults.isLoading ? (
- <LoadingIndicator size={20} mini />
- ) : traceNode.space?.[1] ? (
- getDuration(traceNode.space[1] / 1000, 2, true)
- ) : (
- '\u2014'
- )
- }
- subtext={null}
- />
- </TraceHeaderRow>
- </TraceHeaderContainer>
- );
- }
- const FlexBox = styled('div')`
- display: flex;
- align-items: center;
- `;
- const TraceHeaderContainer = styled(FlexBox)`
- justify-content: space-between;
- `;
- const TraceHeaderRow = styled(FlexBox)<{textAlign: 'left' | 'right'}>`
- gap: ${space(2)};
- text-align: ${p => p.textAlign};
- `;
- const ReplayLinkBody = styled(FlexBox)`
- gap: ${space(0.25)};
- `;
|