mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
228 lines
7.8 KiB
TypeScript
228 lines
7.8 KiB
TypeScript
import {Fragment} from 'react';
|
|
import type {Location} from 'history';
|
|
|
|
import Feature from 'sentry/components/acl/feature';
|
|
import IdBadge from 'sentry/components/idBadge';
|
|
import * as Layout from 'sentry/components/layouts/thirds';
|
|
import type {Organization, Project} from 'sentry/types';
|
|
import DiscoverQuery from 'sentry/utils/discover/discoverQuery';
|
|
import type EventView from 'sentry/utils/discover/eventView';
|
|
import type {ChildrenProps as SpanExamplesProps} from 'sentry/utils/performance/suspectSpans/spanExamplesQuery';
|
|
import SpanExamplesQuery from 'sentry/utils/performance/suspectSpans/spanExamplesQuery';
|
|
import type {ChildrenProps as SuspectSpansProps} from 'sentry/utils/performance/suspectSpans/suspectSpansQuery';
|
|
import SuspectSpansQuery from 'sentry/utils/performance/suspectSpans/suspectSpansQuery';
|
|
import type {SpanSlug} from 'sentry/utils/performance/suspectSpans/types';
|
|
import {setGroupedEntityTag} from 'sentry/utils/performanceForSentry';
|
|
import {decodeScalar} from 'sentry/utils/queryString';
|
|
import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
|
|
import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
|
|
import Breadcrumb from 'sentry/views/performance/breadcrumb';
|
|
import {getSelectedProjectPlatforms} from 'sentry/views/performance/utils';
|
|
|
|
import Tab from '../../tabs';
|
|
import {SpanSortOthers} from '../types';
|
|
import {getTotalsView} from '../utils';
|
|
|
|
import SpanChart from './chart';
|
|
import SpanDetailsControls from './spanDetailsControls';
|
|
import SpanDetailsHeader from './spanDetailsHeader';
|
|
import SpanTable from './spanDetailsTable';
|
|
import {ZoomKeys} from './utils';
|
|
|
|
type Props = {
|
|
eventView: EventView;
|
|
location: Location;
|
|
organization: Organization;
|
|
project: Project | undefined;
|
|
spanSlug: SpanSlug;
|
|
transactionName: string;
|
|
};
|
|
|
|
export default function SpanDetailsContentWrapper(props: Props) {
|
|
const {location, organization, eventView, project, transactionName, spanSlug} = props;
|
|
const minExclusiveTime = decodeScalar(location.query[ZoomKeys.MIN]);
|
|
const maxExclusiveTime = decodeScalar(location.query[ZoomKeys.MAX]);
|
|
|
|
// 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]) : '',
|
|
});
|
|
|
|
return (
|
|
<Fragment>
|
|
<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>
|
|
<Layout.Body>
|
|
<Layout.Main fullWidth>
|
|
<DiscoverQuery
|
|
eventView={getTotalsView(eventView)}
|
|
orgSlug={organization.slug}
|
|
location={location}
|
|
referrer="api.performance.transaction-spans"
|
|
cursor="0:0:1"
|
|
noPagination
|
|
>
|
|
{({tableData}) => {
|
|
const totalCount: number | null =
|
|
(tableData?.data?.[0]?.['count()'] as number) ?? null;
|
|
|
|
if (totalCount) {
|
|
setGroupedEntityTag('spans.totalCount', 1000, totalCount);
|
|
}
|
|
|
|
return (
|
|
<SuspectSpansQuery
|
|
location={location}
|
|
orgSlug={organization.slug}
|
|
eventView={getSpansEventView(eventView)}
|
|
perSuspect={0}
|
|
spanOps={[spanSlug.op]}
|
|
spanGroups={[spanSlug.group]}
|
|
cursor="0:0:1"
|
|
minExclusiveTime={minExclusiveTime}
|
|
maxExclusiveTime={maxExclusiveTime}
|
|
>
|
|
{suspectSpansResults => (
|
|
<SpanExamplesQuery
|
|
location={location}
|
|
orgSlug={organization.slug}
|
|
eventView={eventView}
|
|
spanOp={spanSlug.op}
|
|
spanGroup={spanSlug.group}
|
|
limit={10}
|
|
minExclusiveTime={minExclusiveTime}
|
|
maxExclusiveTime={maxExclusiveTime}
|
|
>
|
|
{spanExamplesResults => (
|
|
<SpanDetailsContent
|
|
location={location}
|
|
organization={organization}
|
|
project={project}
|
|
eventView={eventView}
|
|
spanSlug={spanSlug}
|
|
transactionName={transactionName}
|
|
totalCount={totalCount}
|
|
suspectSpansResults={suspectSpansResults}
|
|
spanExamplesResults={spanExamplesResults}
|
|
/>
|
|
)}
|
|
</SpanExamplesQuery>
|
|
)}
|
|
</SuspectSpansQuery>
|
|
);
|
|
}}
|
|
</DiscoverQuery>
|
|
</Layout.Main>
|
|
</Layout.Body>
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
type ContentProps = {
|
|
eventView: EventView;
|
|
location: Location;
|
|
organization: Organization;
|
|
project: Project | undefined;
|
|
spanExamplesResults: SpanExamplesProps;
|
|
spanSlug: SpanSlug;
|
|
suspectSpansResults: SuspectSpansProps;
|
|
totalCount: number;
|
|
transactionName: string;
|
|
};
|
|
|
|
function SpanDetailsContent(props: ContentProps) {
|
|
const {
|
|
location,
|
|
organization,
|
|
project,
|
|
eventView,
|
|
spanSlug,
|
|
transactionName,
|
|
totalCount,
|
|
suspectSpansResults,
|
|
spanExamplesResults,
|
|
} = props;
|
|
|
|
// There should always be exactly 1 result
|
|
const suspectSpan = suspectSpansResults.suspectSpans?.[0];
|
|
const examples = spanExamplesResults.examples?.[0]?.examples;
|
|
const transactionCountContainingSpan = suspectSpan?.frequency;
|
|
|
|
return (
|
|
<Fragment>
|
|
<Feature features="performance-span-histogram-view">
|
|
<SpanDetailsControls
|
|
organization={organization}
|
|
location={location}
|
|
eventView={eventView}
|
|
/>
|
|
</Feature>
|
|
<SpanDetailsHeader
|
|
spanSlug={spanSlug}
|
|
totalCount={totalCount}
|
|
suspectSpan={suspectSpan}
|
|
/>
|
|
<SpanChart
|
|
totalCount={transactionCountContainingSpan}
|
|
organization={organization}
|
|
eventView={eventView}
|
|
spanSlug={spanSlug}
|
|
/>
|
|
<SpanTable
|
|
location={location}
|
|
organization={organization}
|
|
project={project}
|
|
suspectSpan={suspectSpan}
|
|
transactionName={transactionName}
|
|
isLoading={spanExamplesResults.isLoading}
|
|
examples={examples ?? []}
|
|
pageLinks={spanExamplesResults.pageLinks}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
function getSpansEventView(eventView: EventView): EventView {
|
|
// TODO: There is a bug where if the sort is not avg occurrence,
|
|
// then the avg occurrence will never be added to the fields
|
|
eventView = eventView.withSorts([{field: SpanSortOthers.AVG_OCCURRENCE, kind: 'desc'}]);
|
|
eventView.fields = [
|
|
{field: 'count()'},
|
|
{field: 'count_unique(id)'},
|
|
{field: 'equation|count() / count_unique(id)'},
|
|
{field: 'sumArray(spans_exclusive_time)'},
|
|
{field: 'percentileArray(spans_exclusive_time, 0.50)'},
|
|
{field: 'percentileArray(spans_exclusive_time, 0.75)'},
|
|
{field: 'percentileArray(spans_exclusive_time, 0.95)'},
|
|
{field: 'percentileArray(spans_exclusive_time, 0.99)'},
|
|
];
|
|
return eventView;
|
|
}
|