123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- import {useState} from 'react';
- 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 {RowRectangle} from 'sentry/components/performance/waterfall/rowBar';
- import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
- import PerformanceDuration from 'sentry/components/performanceDuration';
- import TimeSince from 'sentry/components/timeSince';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconIssues} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {DateString} from 'sentry/types/core';
- import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
- import {getShortEventId} from 'sentry/utils/events';
- 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 type {TraceResult} from './content';
- import type {Field} from './data';
- import {getShortenedSdkName, getStylingSliceName} from './utils';
- 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')<{hoveredIndex?: number}>`
- position: relative;
- display: flex;
- min-width: 200px;
- height: 15px;
- background-color: ${p => p.theme.gray100};
- ${p => `--hoveredSlice-${p.hoveredIndex ?? -1}-translateY: translateY(-3px)`};
- `;
- const RectangleTraceBreakdown = styled(RowRectangle)<{
- sliceColor: string;
- sliceName: string | null;
- offset?: number;
- }>`
- background-color: ${p => p.sliceColor};
- position: relative;
- width: 100%;
- height: 15px;
- ${p => `
- filter: var(--highlightedSlice-${p.sliceName}-saturate, var(--defaultSlice-saturate));
- `}
- ${p => `
- opacity: var(--highlightedSlice-${p.sliceName ?? ''}-opacity, var(--defaultSlice-opacity, 1.0));
- `}
- ${p => `
- transform: var(--hoveredSlice-${p.offset}-translateY, var(--highlightedSlice-${p.sliceName ?? ''}-transform, var(--defaultSlice-transform, 1.0)));
- `}
- transition: filter,opacity,transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
- `;
- export function TraceBreakdownRenderer({
- trace,
- setHighlightedSliceName,
- }: {
- setHighlightedSliceName: (sliceName: string) => void;
- trace: TraceResult<Field>;
- }) {
- const theme = useTheme();
- const [hoveredIndex, setHoveredIndex] = useState(-1);
- return (
- <TraceBreakdownContainer
- data-test-id="relative-ops-breakdown"
- hoveredIndex={hoveredIndex}
- onMouseLeave={() => setHoveredIndex(-1)}
- >
- {trace.breakdowns.map((breakdown, index) => {
- return (
- <SpanBreakdownSliceRenderer
- key={breakdown.start + (breakdown.project ?? t('missing instrumentation'))}
- sliceName={breakdown.project}
- sliceStart={breakdown.start}
- sliceEnd={breakdown.end}
- sliceDurationReal={breakdown.duration}
- sliceSecondaryName={breakdown.sdkName}
- trace={trace}
- theme={theme}
- offset={index}
- onMouseEnter={() => {
- setHoveredIndex(index);
- breakdown.project
- ? setHighlightedSliceName(
- getStylingSliceName(breakdown.project, breakdown.sdkName) ?? ''
- )
- : null;
- }}
- />
- );
- })}
- </TraceBreakdownContainer>
- );
- }
- const BREAKDOWN_BAR_SIZE = 200;
- const BREAKDOWN_QUANTIZE_STEP = 1;
- const BREAKDOWN_NUM_SLICES = BREAKDOWN_BAR_SIZE / BREAKDOWN_QUANTIZE_STEP; // 200
- export function SpanBreakdownSliceRenderer({
- trace,
- theme,
- sliceName,
- sliceStart,
- sliceEnd,
- sliceDurationReal,
- sliceSecondaryName,
- onMouseEnter,
- offset,
- }: {
- onMouseEnter: () => void;
- sliceEnd: number;
- sliceName: string | null;
- sliceSecondaryName: string | null;
- sliceStart: number;
- theme: Theme;
- trace: TraceResult<Field>;
- offset?: number;
- sliceDurationReal?: number;
- }) {
- const traceSliceSize = (trace.end - trace.start) / BREAKDOWN_NUM_SLICES;
- const traceDuration = BREAKDOWN_NUM_SLICES * traceSliceSize;
- const sliceDuration = sliceEnd - sliceStart;
- if (sliceDuration <= 0) {
- return null;
- }
- const stylingSliceName = getStylingSliceName(sliceName, sliceSecondaryName);
- const sliceColor = stylingSliceName ? pickBarColor(stylingSliceName) : theme.gray100;
- const sliceWidth =
- BREAKDOWN_QUANTIZE_STEP *
- Math.ceil(BREAKDOWN_NUM_SLICES * (sliceDuration / traceDuration));
- const relativeSliceStart = sliceStart - trace.start;
- const sliceOffset =
- BREAKDOWN_QUANTIZE_STEP *
- Math.floor((BREAKDOWN_NUM_SLICES * relativeSliceStart) / traceDuration);
- return (
- <BreakdownSlice
- sliceName={sliceName}
- sliceOffset={sliceOffset}
- sliceWidth={sliceWidth}
- onMouseEnter={onMouseEnter}
- >
- <Tooltip
- title={
- <div>
- <FlexContainer>
- {sliceName ? <ProjectRenderer projectSlug={sliceName} hideName /> : null}
- <strong>{sliceName}</strong>
- <Subtext>({getShortenedSdkName(sliceSecondaryName)})</Subtext>
- </FlexContainer>
- <div>
- <PerformanceDuration
- milliseconds={sliceDurationReal ?? sliceDuration}
- abbreviation
- />
- </div>
- </div>
- }
- containerDisplayMode="block"
- >
- <RectangleTraceBreakdown
- sliceColor={sliceColor}
- sliceName={stylingSliceName}
- offset={offset}
- />
- </Tooltip>
- </BreakdownSlice>
- );
- }
- const Subtext = styled('span')`
- font-weight: 400;
- color: ${p => p.theme.gray300};
- `;
- const FlexContainer = styled('div')`
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: ${space(0.5)};
- padding-bottom: ${space(0.5)};
- `;
- const BreakdownSlice = styled('div')<{
- sliceName: string | null;
- sliceOffset: number;
- sliceWidth: number;
- }>`
- position: absolute;
- width: max(3px, ${p => p.sliceWidth}px);
- left: ${p => p.sliceOffset}px;
- ${p => (p.sliceName ? null : 'z-index: -1;')}
- `;
- 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({
- projectSlug,
- traceSlug: traceId,
- timestamp,
- eventId: transactionId,
- organization,
- location,
- 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} style={{minWidth: '66px', textAlign: 'right'}}>
- {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 organization = useOrganization();
- const issueCount = trace.numErrors + trace.numOccurrences;
- const issueText = issueCount >= 100 ? '99+' : issueCount === 0 ? '\u2014' : issueCount;
- return (
- <LinkButton
- to={normalizeUrl({
- pathname: `/organizations/${organization.slug}/issues`,
- query: {
- query: `trace:"${trace.trace}"`,
- },
- })}
- size="xs"
- icon={<IconIssues size="xs" />}
- disabled={issueCount === 0}
- style={{minHeight: '24px', height: '24px', minWidth: '44px'}}
- >
- {issueText}
- </LinkButton>
- );
- }
- export function SpanTimeRenderer({
- timestamp,
- tooltipShowSeconds,
- }: {
- timestamp: number;
- tooltipShowSeconds?: boolean;
- }) {
- const date = new Date(timestamp);
- return (
- <TimeSince
- unitStyle="extraShort"
- date={date}
- tooltipShowSeconds={tooltipShowSeconds}
- />
- );
- }
|