123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- import {type Theme, useTheme} from '@emotion/react';
- import styled from '@emotion/styled';
- import {LinkButton} from 'sentry/components/button';
- import ProjectBadge from 'sentry/components/idBadge/projectBadge';
- import Link from 'sentry/components/links/link';
- import LoadingIndicator from 'sentry/components/loadingIndicator';
- import {ROW_HEIGHT, ROW_PADDING} from 'sentry/components/performance/waterfall/constants';
- import {RowRectangle} from 'sentry/components/performance/waterfall/rowBar';
- import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
- import PerformanceDuration from 'sentry/components/performanceDuration';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconIssues} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import type {DateString} from 'sentry/types';
- import EventView from 'sentry/utils/discover/eventView';
- import {
- generateEventSlug,
- generateLinkToEventInTraceView,
- } from 'sentry/utils/discover/urls';
- import {getShortEventId} from 'sentry/utils/events';
- import toPercent from 'sentry/utils/number/toPercent';
- import Projects from 'sentry/utils/projects';
- import {useLocation} from 'sentry/utils/useLocation';
- import useOrganization from 'sentry/utils/useOrganization';
- import usePageFilters from 'sentry/utils/usePageFilters';
- import useProjects from 'sentry/utils/useProjects';
- import {normalizeUrl} from 'sentry/utils/withDomainRequired';
- import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
- import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils';
- import {useTraceMeta} from '../newTraceDetails/traceApi/useTraceMeta';
- import type {TraceResult} from './content';
- import type {Field} from './data';
- interface ProjectRendererProps {
- projectSlug: string;
- hideName?: boolean;
- }
- export function ProjectRenderer({projectSlug, hideName}: ProjectRendererProps) {
- const organization = useOrganization();
- return (
- <Projects orgId={organization.slug} slugs={[projectSlug]}>
- {({projects}) => {
- const project = projects.find(p => p.slug === projectSlug);
- return (
- <ProjectBadge
- hideName={hideName}
- project={project ? project : {slug: projectSlug}}
- avatarSize={16}
- />
- );
- }}
- </Projects>
- );
- }
- export const TraceBreakdownContainer = styled('div')`
- position: relative;
- display: flex;
- min-width: 150px;
- height: ${ROW_HEIGHT - 2 * ROW_PADDING}px;
- background-color: ${p => p.theme.gray100};
- `;
- const RectangleTraceBreakdown = styled(RowRectangle)`
- position: relative;
- width: 100%;
- `;
- export function TraceBreakdownRenderer({trace}: {trace: TraceResult<Field>}) {
- const theme = useTheme();
- return (
- <TraceBreakdownContainer data-test-id="relative-ops-breakdown">
- {trace.breakdowns.map(breakdown => {
- return (
- <SpanBreakdownSliceRenderer
- key={breakdown.start + (breakdown.project ?? t('missing instrumentation'))}
- sliceName={breakdown.project}
- sliceStart={breakdown.start}
- sliceEnd={breakdown.end}
- trace={trace}
- theme={theme}
- />
- );
- })}
- </TraceBreakdownContainer>
- );
- }
- export function SpanBreakdownSliceRenderer({
- trace,
- theme,
- sliceName,
- sliceStart,
- sliceEnd,
- }: {
- sliceEnd: number;
- sliceName: string | null;
- sliceStart: number;
- theme: Theme;
- trace: TraceResult<Field>;
- }) {
- const traceDuration = trace.end - trace.start;
- const sliceDuration = sliceEnd - sliceStart;
- if (sliceDuration <= 0) {
- return null;
- }
- const sliceColor = sliceName ? pickBarColor(sliceName) : theme.gray100;
- const slicePercent = toPercent(sliceDuration / traceDuration);
- const relativeSliceStart = sliceStart - trace.start;
- const sliceOffset = toPercent(relativeSliceStart / traceDuration);
- return (
- <div style={{width: slicePercent, left: sliceOffset, position: 'absolute'}}>
- <Tooltip
- title={
- <div>
- <div>{sliceName}</div>
- <div>
- <PerformanceDuration milliseconds={sliceDuration} abbreviation />
- </div>
- </div>
- }
- containerDisplayMode="block"
- >
- <RectangleTraceBreakdown
- style={{
- backgroundColor: sliceColor,
- }}
- onClick={_ => {}}
- />
- </Tooltip>
- </div>
- );
- }
- interface SpanIdRendererProps {
- projectSlug: string;
- spanId: string;
- timestamp: string;
- traceId: string;
- transactionId: string;
- }
- export function SpanIdRenderer({
- projectSlug,
- spanId,
- timestamp,
- traceId,
- transactionId,
- }: SpanIdRendererProps) {
- const location = useLocation();
- const organization = useOrganization();
- const target = generateLinkToEventInTraceView({
- eventSlug: generateEventSlug({
- id: transactionId,
- project: projectSlug,
- }),
- organization,
- location,
- eventView: EventView.fromLocation(location),
- dataRow: {
- id: transactionId,
- trace: traceId,
- timestamp,
- },
- spanId,
- });
- return <Link to={target}>{getShortEventId(spanId)}</Link>;
- }
- interface TraceIdRendererProps {
- traceId: string;
- timestamp?: DateString;
- transactionId?: string;
- }
- export function TraceIdRenderer({
- traceId,
- timestamp,
- transactionId,
- }: TraceIdRendererProps) {
- const organization = useOrganization();
- const {selection} = usePageFilters();
- const stringOrNumberTimestamp =
- timestamp instanceof Date ? timestamp.toISOString() : timestamp ?? '';
- const target = getTraceDetailsUrl(
- organization,
- traceId,
- {
- start: selection.datetime.start,
- end: selection.datetime.end,
- statsPeriod: selection.datetime.period,
- },
- {},
- stringOrNumberTimestamp,
- transactionId
- );
- return <Link to={target}>{getShortEventId(traceId)}</Link>;
- }
- interface TransactionRendererProps {
- projectSlug: string;
- transaction: string;
- }
- export function TransactionRenderer({
- projectSlug,
- transaction,
- }: TransactionRendererProps) {
- const location = useLocation();
- const organization = useOrganization();
- const {projects} = useProjects({slugs: [projectSlug]});
- const target = transactionSummaryRouteWithQuery({
- orgSlug: organization.slug,
- transaction,
- query: {
- ...location.query,
- query: undefined,
- },
- projectID: String(projects[0]?.id ?? ''),
- });
- return <Link to={target}>{transaction}</Link>;
- }
- export function TraceIssuesRenderer({trace}: {trace: TraceResult<Field>}) {
- const traceMeta = useTraceMeta(trace.trace);
- const organization = useOrganization();
- const issueCount = !traceMeta.data
- ? undefined
- : traceMeta.data.errors + traceMeta.data.performance_issues;
- return (
- <LinkButton
- to={normalizeUrl({
- pathname: `/organizations/${organization.slug}/issues`,
- query: {
- query: `is:unresolved trace:"${trace.trace}"`,
- },
- })}
- size="xs"
- icon={<IconIssues size="xs" />}
- >
- {issueCount !== undefined ? (
- issueCount
- ) : (
- <LoadingIndicator
- size={12}
- mini
- style={{height: '12px', width: '12px', margin: 0, marginRight: 0}}
- />
- )}
- {issueCount === 100 && '+'}
- </LinkButton>
- );
- }
|