123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- import {Fragment} from 'react';
- import type {Location} from 'history';
- import IdBadge from 'sentry/components/idBadge';
- import * as Layout from 'sentry/components/layouts/thirds';
- import type {Organization} from 'sentry/types/organization';
- import type {Project} from 'sentry/types/project';
- import type {SpanSlug} from 'sentry/utils/performance/suspectSpans/types';
- import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
- import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
- import {MutableSearch} from 'sentry/utils/tokenizeSearch';
- import {useLocation} from 'sentry/utils/useLocation';
- import {useParams} from 'sentry/utils/useParams';
- import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
- import {AiHeader} from 'sentry/views/insights/pages/ai/aiPageHeader';
- import {BackendHeader} from 'sentry/views/insights/pages/backend/backendPageHeader';
- import {FrontendHeader} from 'sentry/views/insights/pages/frontend/frontendPageHeader';
- import {MobileHeader} from 'sentry/views/insights/pages/mobile/mobilePageHeader';
- import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters';
- import type {
- SpanMetricsQueryFilters,
- SpanMetricsResponse,
- } from 'sentry/views/insights/types';
- import Breadcrumb, {getTabCrumbs} from 'sentry/views/performance/breadcrumb';
- import {SpanSummaryReferrer} from 'sentry/views/performance/transactionSummary/transactionSpans/spanSummary/referrers';
- import SpanSummaryCharts from 'sentry/views/performance/transactionSummary/transactionSpans/spanSummary/spanSummaryCharts';
- import SpanSummaryTable from 'sentry/views/performance/transactionSummary/transactionSpans/spanSummary/spanSummaryTable';
- import {getSelectedProjectPlatforms} from 'sentry/views/performance/utils';
- import Tab from '../../tabs';
- import SpanSummaryControls from './spanSummaryControls';
- import SpanSummaryHeader from './spanSummaryHeader';
- type Props = {
- organization: Organization;
- project: Project | undefined;
- spanSlug: SpanSlug;
- transactionName: string;
- };
- export default function SpanSummary(props: Props) {
- const {organization, project, transactionName, spanSlug} = props;
- const location = useLocation();
- const {isInDomainView, view} = useDomainViewFilters();
- // customize the route analytics event we send
- useRouteAnalyticsEventNames(
- 'performance_views.span_summary.view',
- 'Performance Views: Span Summary page viewed'
- );
- useRouteAnalyticsParams({
- project_platforms: project ? getSelectedProjectPlatforms(location, [project]) : '',
- });
- const domainViewHeaderProps = {
- headerTitle: (
- <Fragment>
- {project && (
- <IdBadge
- project={project}
- avatarSize={28}
- hideName
- avatarProps={{hasTooltip: true, tooltip: project.slug}}
- />
- )}
- {transactionName}
- </Fragment>
- ),
- breadcrumbs: getTabCrumbs({
- organization,
- location,
- transaction: {name: transactionName, project: project?.id ?? ''},
- tab: Tab.SPANS,
- spanSlug,
- view,
- }),
- hideDefaultTabs: true,
- };
- return (
- <Fragment>
- {!isInDomainView && (
- <Layout.Header>
- <Layout.HeaderContent>
- <Breadcrumb
- organization={organization}
- location={location}
- transaction={{
- project: project?.id ?? '',
- name: transactionName,
- }}
- tab={Tab.SPANS}
- spanSlug={spanSlug}
- />
- <Layout.Title>
- {project && (
- <IdBadge
- project={project}
- avatarSize={28}
- hideName
- avatarProps={{hasTooltip: true, tooltip: project.slug}}
- />
- )}
- {transactionName}
- </Layout.Title>
- </Layout.HeaderContent>
- </Layout.Header>
- )}
- {isInDomainView && view === 'frontend' && (
- <FrontendHeader {...domainViewHeaderProps} />
- )}
- {isInDomainView && view === 'backend' && (
- <BackendHeader {...domainViewHeaderProps} />
- )}
- {isInDomainView && view === 'mobile' && <MobileHeader {...domainViewHeaderProps} />}
- {isInDomainView && view === 'ai' && <AiHeader {...domainViewHeaderProps} />}
- <Layout.Body>
- <Layout.Main fullWidth>
- <SpanSummaryContent
- location={location}
- organization={organization}
- project={project}
- transactionName={transactionName}
- />
- </Layout.Main>
- </Layout.Body>
- </Fragment>
- );
- }
- type ContentProps = {
- location: Location;
- organization: Organization;
- project: Project | undefined;
- transactionName: string;
- };
- function SpanSummaryContent(props: ContentProps) {
- const {transactionName, project} = props;
- const {spanSlug: spanParam} = useParams();
- const [spanOp, groupId] = spanParam.split(':');
- const filters: SpanMetricsQueryFilters = {
- 'span.group': groupId,
- 'span.op': spanOp,
- transaction: transactionName,
- };
- const {data: spanHeaderData} = useSpanMetrics(
- {
- search: MutableSearch.fromQueryObject(filters),
- fields: ['span.description', 'sum(span.duration)', 'count()'],
- sorts: [{field: 'sum(span.duration)', kind: 'desc'}],
- },
- SpanSummaryReferrer.SPAN_SUMMARY_HEADER_DATA
- );
- // Average span duration must be queried for separately, since it could get broken up into multiple groups if used in the first query
- const {data: avgDurationData} = useSpanMetrics(
- {
- search: MutableSearch.fromQueryObject(filters),
- fields: ['avg(span.duration)'],
- },
- SpanSummaryReferrer.SPAN_SUMMARY_HEADER_DATA
- );
- const parsedData = parseSpanHeaderData(spanHeaderData);
- return (
- <Fragment>
- <SpanSummaryControls />
- <SpanSummaryHeader
- spanOp={spanOp}
- spanDescription={parsedData?.description}
- avgDuration={avgDurationData[0]?.['avg(span.duration)']}
- timeSpent={parsedData?.timeSpent}
- spanCount={parsedData?.spanCount}
- />
- <SpanSummaryCharts />
- <SpanSummaryTable project={project} />
- </Fragment>
- );
- }
- function parseSpanHeaderData(data: Partial<SpanMetricsResponse>[]) {
- if (!data || data.length === 0) {
- return undefined;
- }
- if (data.length === 1) {
- return {
- description: data[0]?.['span.description'],
- timeSpent: data[0]?.['sum(span.duration)'],
- spanCount: data[0]?.['count()'],
- };
- }
- const cumulativeData = {
- description: undefined,
- timeSpent: 0,
- spanCount: 0,
- };
- data.forEach(datum => {
- cumulativeData.timeSpent += datum['sum(span.self_time)'] ?? 0;
- cumulativeData.spanCount += datum['count()'] ?? 0;
- });
- return cumulativeData;
- }
|