mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-22 18:06:35 +00:00
feat: add support for span hover card in trace details v2 (#8930)
* feat: add support for span hover card in trace details v2 * chore: remove the unnecessary tooltip --------- Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
This commit is contained in:
parent
011b769d4d
commit
f91115948a
108
frontend/src/components/SpanHoverCard/SpanHoverCard.styles.scss
Normal file
108
frontend/src/components/SpanHoverCard/SpanHoverCard.styles.scss
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
.span-hover-card {
|
||||||
|
width: 206px;
|
||||||
|
|
||||||
|
.ant-popover-inner {
|
||||||
|
background: linear-gradient(
|
||||||
|
139deg,
|
||||||
|
rgba(18, 19, 23, 0.32) 0%,
|
||||||
|
rgba(18, 19, 23, 0.36) 98.68%
|
||||||
|
);
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
139deg,
|
||||||
|
rgba(18, 19, 23, 0.32) 0%,
|
||||||
|
rgba(18, 19, 23, 0.36) 98.68%
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: -1;
|
||||||
|
will-change: background-color, backdrop-filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__operation {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 0.48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__service {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--bg-cherry-500);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 174px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__relative-time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 4px;
|
||||||
|
gap: 8px;
|
||||||
|
border-radius: 1px 0 0 1px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
hsla(358, 75%, 59%, 0.2) 0%,
|
||||||
|
rgba(229, 72, 77, 0) 100%
|
||||||
|
);
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
width: 2px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-cherry-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__relative-text {
|
||||||
|
color: var(--bg-cherry-300);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
101
frontend/src/components/SpanHoverCard/SpanHoverCard.tsx
Normal file
101
frontend/src/components/SpanHoverCard/SpanHoverCard.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import './SpanHoverCard.styles.scss';
|
||||||
|
|
||||||
|
import { Popover, Typography } from 'antd';
|
||||||
|
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||||
|
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { Span } from 'types/api/trace/getTraceV2';
|
||||||
|
import { toFixed } from 'utils/toFixed';
|
||||||
|
|
||||||
|
interface ITraceMetadata {
|
||||||
|
startTime: number;
|
||||||
|
endTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpanHoverCardProps {
|
||||||
|
span: Span;
|
||||||
|
traceMetadata: ITraceMetadata;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SpanHoverCard({
|
||||||
|
span,
|
||||||
|
traceMetadata,
|
||||||
|
children,
|
||||||
|
}: SpanHoverCardProps): JSX.Element {
|
||||||
|
const duration = span.durationNano / 1e6; // Convert nanoseconds to milliseconds
|
||||||
|
const { time: formattedDuration, timeUnitName } = convertTimeToRelevantUnit(
|
||||||
|
duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate relative start time from trace start
|
||||||
|
const relativeStartTime = span.timestamp - traceMetadata.startTime;
|
||||||
|
const {
|
||||||
|
time: relativeTime,
|
||||||
|
timeUnitName: relativeTimeUnit,
|
||||||
|
} = convertTimeToRelevantUnit(relativeStartTime);
|
||||||
|
|
||||||
|
// Format absolute start time
|
||||||
|
const startTimeFormatted = dayjs(span.timestamp).format(
|
||||||
|
DATE_TIME_FORMATS.SPAN_POPOVER_DATE,
|
||||||
|
);
|
||||||
|
|
||||||
|
const getContent = (): JSX.Element => (
|
||||||
|
<div className="span-hover-card">
|
||||||
|
<div className="span-hover-card__row">
|
||||||
|
<Typography.Text className="span-hover-card__label">
|
||||||
|
Duration:
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text className="span-hover-card__value">
|
||||||
|
{toFixed(formattedDuration, 2)}
|
||||||
|
{timeUnitName}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="span-hover-card__row">
|
||||||
|
<Typography.Text className="span-hover-card__label">
|
||||||
|
Events:
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text className="span-hover-card__value">
|
||||||
|
{span.event?.length || 0}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="span-hover-card__row">
|
||||||
|
<Typography.Text className="span-hover-card__label">
|
||||||
|
Start time:
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text className="span-hover-card__value">
|
||||||
|
{startTimeFormatted}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="span-hover-card__relative-time">
|
||||||
|
<div className="span-hover-card__relative-time-icon" />
|
||||||
|
<Typography.Text className="span-hover-card__relative-text">
|
||||||
|
{toFixed(relativeTime, 2)}
|
||||||
|
{relativeTimeUnit} after trace start
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
title={
|
||||||
|
<div className="span-hover-card__title">
|
||||||
|
<Typography.Text className="span-hover-card__operation">
|
||||||
|
{span.name}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
content={getContent()}
|
||||||
|
trigger="hover"
|
||||||
|
rootClassName="span-hover-card"
|
||||||
|
autoAdjustOverflow
|
||||||
|
arrow={false}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SpanHoverCard;
|
||||||
@ -29,6 +29,7 @@ export const DATE_TIME_FORMATS = {
|
|||||||
DATE_SHORT: 'MM/DD',
|
DATE_SHORT: 'MM/DD',
|
||||||
YEAR_SHORT: 'YY',
|
YEAR_SHORT: 'YY',
|
||||||
YEAR_MONTH: 'YY-MM',
|
YEAR_MONTH: 'YY-MM',
|
||||||
|
SPAN_POPOVER_DATE: 'M/D/YY - HH:mm',
|
||||||
|
|
||||||
// Month name formats
|
// Month name formats
|
||||||
MONTH_DATE_FULL: 'MMMM DD, YYYY',
|
MONTH_DATE_FULL: 'MMMM DD, YYYY',
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Virtualizer } from '@tanstack/react-virtual';
|
|||||||
import { Button, Tooltip, Typography } from 'antd';
|
import { Button, Tooltip, Typography } from 'antd';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import HttpStatusBadge from 'components/HttpStatusBadge/HttpStatusBadge';
|
import HttpStatusBadge from 'components/HttpStatusBadge/HttpStatusBadge';
|
||||||
|
import SpanHoverCard from 'components/SpanHoverCard/SpanHoverCard';
|
||||||
import { TableV3 } from 'components/TableV3/TableV3';
|
import { TableV3 } from 'components/TableV3/TableV3';
|
||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||||
@ -66,6 +67,7 @@ function SpanOverview({
|
|||||||
setSelectedSpan,
|
setSelectedSpan,
|
||||||
handleAddSpanToFunnel,
|
handleAddSpanToFunnel,
|
||||||
selectedSpan,
|
selectedSpan,
|
||||||
|
traceMetadata,
|
||||||
}: {
|
}: {
|
||||||
span: Span;
|
span: Span;
|
||||||
isSpanCollapsed: boolean;
|
isSpanCollapsed: boolean;
|
||||||
@ -73,6 +75,7 @@ function SpanOverview({
|
|||||||
selectedSpan: Span | undefined;
|
selectedSpan: Span | undefined;
|
||||||
setSelectedSpan: Dispatch<SetStateAction<Span | undefined>>;
|
setSelectedSpan: Dispatch<SetStateAction<Span | undefined>>;
|
||||||
handleAddSpanToFunnel: (span: Span) => void;
|
handleAddSpanToFunnel: (span: Span) => void;
|
||||||
|
traceMetadata: ITraceMetadata;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const isRootSpan = span.level === 0;
|
const isRootSpan = span.level === 0;
|
||||||
const { hasEditPermission } = useAppContext();
|
const { hasEditPermission } = useAppContext();
|
||||||
@ -83,109 +86,111 @@ function SpanOverview({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<SpanHoverCard span={span} traceMetadata={traceMetadata}>
|
||||||
className={cx(
|
<div
|
||||||
'span-overview',
|
className={cx(
|
||||||
selectedSpan?.spanId === span.spanId ? 'interested-span' : '',
|
'span-overview',
|
||||||
)}
|
selectedSpan?.spanId === span.spanId ? 'interested-span' : '',
|
||||||
style={{
|
)}
|
||||||
paddingLeft: `${
|
style={{
|
||||||
isRootSpan
|
paddingLeft: `${
|
||||||
? span.level * CONNECTOR_WIDTH
|
isRootSpan
|
||||||
: (span.level - 1) * (CONNECTOR_WIDTH + VERTICAL_CONNECTOR_WIDTH)
|
? span.level * CONNECTOR_WIDTH
|
||||||
}px`,
|
: (span.level - 1) * (CONNECTOR_WIDTH + VERTICAL_CONNECTOR_WIDTH)
|
||||||
backgroundImage: `url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="54"><line x1="0" y1="0" x2="0" y2="54" stroke="rgb(29 33 45)" stroke-width="1" /></svg>')`,
|
}px`,
|
||||||
backgroundRepeat: 'repeat',
|
backgroundImage: `url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="54"><line x1="0" y1="0" x2="0" y2="54" stroke="rgb(29 33 45)" stroke-width="1" /></svg>')`,
|
||||||
backgroundSize: `${CONNECTOR_WIDTH + 1}px 54px`,
|
backgroundRepeat: 'repeat',
|
||||||
}}
|
backgroundSize: `${CONNECTOR_WIDTH + 1}px 54px`,
|
||||||
onClick={(): void => {
|
}}
|
||||||
setSelectedSpan(span);
|
onClick={(): void => {
|
||||||
}}
|
setSelectedSpan(span);
|
||||||
>
|
}}
|
||||||
{!isRootSpan && (
|
>
|
||||||
<div className="connector-lines">
|
{!isRootSpan && (
|
||||||
<div
|
<div className="connector-lines">
|
||||||
style={{
|
<div
|
||||||
width: `${CONNECTOR_WIDTH}px`,
|
style={{
|
||||||
height: '1px',
|
width: `${CONNECTOR_WIDTH}px`,
|
||||||
borderTop: '1px solid var(--bg-slate-400)',
|
height: '1px',
|
||||||
display: 'flex',
|
borderTop: '1px solid var(--bg-slate-400)',
|
||||||
flexShrink: 0,
|
display: 'flex',
|
||||||
position: 'relative',
|
flexShrink: 0,
|
||||||
top: '-10px',
|
position: 'relative',
|
||||||
}}
|
top: '-10px',
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
)}
|
|
||||||
<div className="span-overview-content">
|
|
||||||
<section className="first-row">
|
|
||||||
<div className="span-det">
|
|
||||||
{span.hasChildren ? (
|
|
||||||
<Button
|
|
||||||
onClick={(event): void => {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
handleCollapseUncollapse(span.spanId, !isSpanCollapsed);
|
|
||||||
}}
|
|
||||||
className="collapse-uncollapse-button"
|
|
||||||
>
|
|
||||||
{isSpanCollapsed ? (
|
|
||||||
<ChevronRight size={14} />
|
|
||||||
) : (
|
|
||||||
<ChevronDown size={14} />
|
|
||||||
)}
|
|
||||||
<Typography.Text className="children-count">
|
|
||||||
{span.subTreeNodeCount}
|
|
||||||
</Typography.Text>
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button className="collapse-uncollapse-button">
|
|
||||||
<Leaf size={14} />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Typography.Text className="span-name">{span.name}</Typography.Text>
|
|
||||||
</div>
|
</div>
|
||||||
<HttpStatusBadge statusCode={span.tagMap?.['http.status_code']} />
|
)}
|
||||||
</section>
|
<div className="span-overview-content">
|
||||||
<section className="second-row">
|
<section className="first-row">
|
||||||
<div style={{ width: '2px', background: color, height: '100%' }} />
|
<div className="span-det">
|
||||||
<Typography.Text className="service-name">
|
{span.hasChildren ? (
|
||||||
{span.serviceName}
|
|
||||||
</Typography.Text>
|
|
||||||
{!!span.serviceName && !!span.name && (
|
|
||||||
<div className="add-funnel-button">
|
|
||||||
<span className="add-funnel-button__separator">·</span>
|
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
!hasEditPermission
|
|
||||||
? 'You need editor or admin access to add spans to funnels'
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
onClick={(event): void => {
|
||||||
size="small"
|
event.stopPropagation();
|
||||||
className="add-funnel-button__button"
|
event.preventDefault();
|
||||||
onClick={(e): void => {
|
handleCollapseUncollapse(span.spanId, !isSpanCollapsed);
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
handleAddSpanToFunnel(span);
|
|
||||||
}}
|
}}
|
||||||
disabled={!hasEditPermission}
|
className="collapse-uncollapse-button"
|
||||||
icon={
|
>
|
||||||
<img
|
{isSpanCollapsed ? (
|
||||||
className="add-funnel-button__icon"
|
<ChevronRight size={14} />
|
||||||
src="/Icons/funnel-add.svg"
|
) : (
|
||||||
alt="funnel-icon"
|
<ChevronDown size={14} />
|
||||||
/>
|
)}
|
||||||
}
|
<Typography.Text className="children-count">
|
||||||
/>
|
{span.subTreeNodeCount}
|
||||||
</Tooltip>
|
</Typography.Text>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button className="collapse-uncollapse-button">
|
||||||
|
<Leaf size={14} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Typography.Text className="span-name">{span.name}</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<HttpStatusBadge statusCode={span.tagMap?.['http.status_code']} />
|
||||||
</section>
|
</section>
|
||||||
|
<section className="second-row">
|
||||||
|
<div style={{ width: '2px', background: color, height: '100%' }} />
|
||||||
|
<Typography.Text className="service-name">
|
||||||
|
{span.serviceName}
|
||||||
|
</Typography.Text>
|
||||||
|
{!!span.serviceName && !!span.name && (
|
||||||
|
<div className="add-funnel-button">
|
||||||
|
<span className="add-funnel-button__separator">·</span>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
!hasEditPermission
|
||||||
|
? 'You need editor or admin access to add spans to funnels'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
className="add-funnel-button__button"
|
||||||
|
onClick={(e): void => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleAddSpanToFunnel(span);
|
||||||
|
}}
|
||||||
|
disabled={!hasEditPermission}
|
||||||
|
icon={
|
||||||
|
<img
|
||||||
|
className="add-funnel-button__icon"
|
||||||
|
src="/Icons/funnel-add.svg"
|
||||||
|
alt="funnel-icon"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SpanHoverCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,64 +254,64 @@ export function SpanDuration({
|
|||||||
}, [leftOffset, width, color]);
|
}, [leftOffset, width, color]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<SpanHoverCard span={span} traceMetadata={traceMetadata}>
|
||||||
className={cx(
|
|
||||||
'span-duration',
|
|
||||||
selectedSpan?.spanId === span.spanId ? 'interested-span' : '',
|
|
||||||
)}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
onClick={(): void => {
|
|
||||||
setSelectedSpan(span);
|
|
||||||
if (span?.spanId) {
|
|
||||||
urlQuery.set('spanId', span?.spanId);
|
|
||||||
}
|
|
||||||
|
|
||||||
safeNavigate({ search: urlQuery.toString() });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="span-line"
|
className={cx(
|
||||||
style={{
|
'span-duration',
|
||||||
left: `${leftOffset}%`,
|
selectedSpan?.spanId === span.spanId ? 'interested-span' : '',
|
||||||
width: `${width}%`,
|
)}
|
||||||
backgroundColor: color,
|
onMouseEnter={handleMouseEnter}
|
||||||
position: 'relative',
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onClick={(): void => {
|
||||||
|
setSelectedSpan(span);
|
||||||
|
if (span?.spanId) {
|
||||||
|
urlQuery.set('spanId', span?.spanId);
|
||||||
|
}
|
||||||
|
|
||||||
|
safeNavigate({ search: urlQuery.toString() });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{span.event?.map((event) => {
|
<div
|
||||||
const eventTimeMs = event.timeUnixNano / 1e6;
|
className="span-line"
|
||||||
const eventOffsetPercent =
|
style={{
|
||||||
((eventTimeMs - span.timestamp) / (span.durationNano / 1e6)) * 100;
|
left: `${leftOffset}%`,
|
||||||
const clampedOffset = Math.max(1, Math.min(eventOffsetPercent, 99));
|
width: `${width}%`,
|
||||||
const { isError } = event;
|
backgroundColor: color,
|
||||||
const { time, timeUnitName } = convertTimeToRelevantUnit(
|
position: 'relative',
|
||||||
eventTimeMs - span.timestamp,
|
}}
|
||||||
);
|
>
|
||||||
return (
|
{span.event?.map((event) => {
|
||||||
<Tooltip
|
const eventTimeMs = event.timeUnixNano / 1e6;
|
||||||
key={`${span.spanId}-event-${event.name}-${event.timeUnixNano}`}
|
const eventOffsetPercent =
|
||||||
title={`${event.name} @ ${toFixed(time, 2)} ${timeUnitName}`}
|
((eventTimeMs - span.timestamp) / (span.durationNano / 1e6)) * 100;
|
||||||
>
|
const clampedOffset = Math.max(1, Math.min(eventOffsetPercent, 99));
|
||||||
<div
|
const { isError } = event;
|
||||||
className={`event-dot ${isError ? 'error' : ''}`}
|
const { time, timeUnitName } = convertTimeToRelevantUnit(
|
||||||
style={{
|
eventTimeMs - span.timestamp,
|
||||||
left: `${clampedOffset}%`,
|
);
|
||||||
}}
|
return (
|
||||||
/>
|
<Tooltip
|
||||||
</Tooltip>
|
key={`${span.spanId}-event-${event.name}-${event.timeUnixNano}`}
|
||||||
);
|
title={`${event.name} @ ${toFixed(time, 2)} ${timeUnitName}`}
|
||||||
})}
|
>
|
||||||
</div>
|
<div
|
||||||
{hasActionButtons && <SpanLineActionButtons span={span} />}
|
className={`event-dot ${isError ? 'error' : ''}`}
|
||||||
<Tooltip title={`${toFixed(time, 2)} ${timeUnitName}`}>
|
style={{
|
||||||
|
left: `${clampedOffset}%`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{hasActionButtons && <SpanLineActionButtons span={span} />}
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
className="span-line-text"
|
className="span-line-text"
|
||||||
ellipsis
|
ellipsis
|
||||||
style={textStyle}
|
style={textStyle}
|
||||||
>{`${toFixed(time, 2)} ${timeUnitName}`}</Typography.Text>
|
>{`${toFixed(time, 2)} ${timeUnitName}`}</Typography.Text>
|
||||||
</Tooltip>
|
</div>
|
||||||
</div>
|
</SpanHoverCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +346,7 @@ function getWaterfallColumns({
|
|||||||
selectedSpan={selectedSpan}
|
selectedSpan={selectedSpan}
|
||||||
setSelectedSpan={setSelectedSpan}
|
setSelectedSpan={setSelectedSpan}
|
||||||
handleAddSpanToFunnel={handleAddSpanToFunnel}
|
handleAddSpanToFunnel={handleAddSpanToFunnel}
|
||||||
|
traceMetadata={traceMetadata}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
size: 450,
|
size: 450,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user