;
searchResultsIteratorIndex: number | null;
style: React.CSSProperties;
tabIndex: number;
theme: Theme;
trace_id: string;
}) {
const virtualized_index = props.index - props.manager.start_virtualized_index;
const rowSearchClassName = `${props.isSearchResult ? 'SearchResult' : ''} ${props.searchResultsIteratorIndex === props.index ? 'Highlight' : ''}`;
if (isAutogroupedNode(props.node)) {
return (
props.tabIndex === props.index
? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
: null
}
tabIndex={props.tabIndex === props.index ? 0 : -1}
className={`Autogrouped TraceRow ${rowSearchClassName} ${props.node.has_errors ? 'Errored' : ''}`}
onClick={e => props.onRowClick(e, props.index, props.node)}
onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
style={{
top: props.style.top,
height: props.style.height,
}}
>
props.manager.registerColumnRef('list', r, virtualized_index, props.node)
}
>
) : (
)
}
status={props.node.fetchStatus}
expanded={!props.node.expanded}
onClick={e => props.onExpand(e, props.node, !props.node.expanded)}
errored={props.node.has_errors}
>
{COUNT_FORMATTER.format(props.node.groupCount)}
{t('Autogrouped')}
—
{props.node.value.autogrouped_by.op}
props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
}
onDoubleClick={e => {
e.stopPropagation();
props.manager.onZoomIntoSpace(props.node.space!);
}}
>
);
}
if (isTransactionNode(props.node)) {
const errored =
props.node.value.errors.length > 0 ||
props.node.value.performance_issues.length > 0;
return (
props.tabIndex === props.index
? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
: null
}
tabIndex={props.tabIndex === props.index ? 0 : -1}
className={`TraceRow ${rowSearchClassName} ${errored ? 'Errored' : ''}`}
onClick={e => props.onRowClick(e, props.index, props.node)}
onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
style={{
top: props.style.top,
height: props.style.height,
}}
>
props.manager.registerColumnRef('list', r, virtualized_index, props.node)
}
>
{props.node.children.length > 0 || props.node.canFetch ? (
) : (
'+'
)
}
status={props.node.fetchStatus}
expanded={props.node.expanded || props.node.zoomedIn}
onClick={e =>
props.node.canFetch
? props.onZoomIn(e, props.node, !props.node.zoomedIn)
: props.onExpand(e, props.node, !props.node.expanded)
}
errored={errored}
>
{props.node.children.length > 0
? COUNT_FORMATTER.format(props.node.children.length)
: null}
) : null}
{props.node.value['transaction.op']}
—
{props.node.value.transaction}
props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
}
className={
props.index % 2 === 1
? RIGHT_COLUMN_ODD_CLASSNAME
: RIGHT_COLUMN_EVEN_CLASSNAME
}
onDoubleClick={e => {
e.stopPropagation();
props.manager.onZoomIntoSpace(props.node.space!);
}}
>
);
}
if (isSpanNode(props.node)) {
const errored = props.node.errors.size > 0 || props.node.performance_issues.size > 0;
return (
props.tabIndex === props.index
? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
: null
}
tabIndex={props.tabIndex === props.index ? 0 : -1}
className={`TraceRow ${rowSearchClassName} ${errored ? 'Errored' : ''}`}
onClick={e => props.onRowClick(e, props.index, props.node)}
onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
style={{
top: props.style.top,
height: props.style.height,
}}
>
props.manager.registerColumnRef('list', r, virtualized_index, props.node)
}
>
{props.node.children.length > 0 || props.node.canFetch ? (
) : (
)
}
status={props.node.fetchStatus}
expanded={props.node.expanded || props.node.zoomedIn}
onClick={e =>
props.node.canFetch
? props.onZoomIn(e, props.node, !props.node.zoomedIn)
: props.onExpand(e, props.node, !props.node.expanded)
}
errored={errored}
>
{props.node.children.length > 0
? COUNT_FORMATTER.format(props.node.children.length)
: null}
) : null}
{props.node.value.op ?? ''}
—
{!props.node.value.description
? 'unknown'
: props.node.value.description.length > 100
? props.node.value.description.slice(0, 100).trim() + '\u2026'
: props.node.value.description}
props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
}
className={
props.index % 2 === 1
? RIGHT_COLUMN_ODD_CLASSNAME
: RIGHT_COLUMN_EVEN_CLASSNAME
}
onDoubleClick={e => {
e.stopPropagation();
props.manager.onZoomIntoSpace(props.node.space!);
}}
>
);
}
if (isMissingInstrumentationNode(props.node)) {
return (
props.tabIndex === props.index
? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
: null
}
tabIndex={props.tabIndex === props.index ? 0 : -1}
className={`TraceRow ${rowSearchClassName}`}
onClick={e => props.onRowClick(e, props.index, props.node)}
onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
style={{
top: props.style.top,
height: props.style.height,
}}
>
props.manager.registerColumnRef('list', r, virtualized_index, props.node)
}
>
{t('Missing instrumentation')}
props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
}
className={
props.index % 2 === 1
? RIGHT_COLUMN_ODD_CLASSNAME
: RIGHT_COLUMN_EVEN_CLASSNAME
}
onDoubleClick={e => {
e.stopPropagation();
props.manager.onZoomIntoSpace(props.node.space!);
}}
>
);
}
if (isTraceNode(props.node)) {
return (
props.tabIndex === props.index
? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
: null
}
tabIndex={props.tabIndex === props.index ? 0 : -1}
className={`TraceRow ${rowSearchClassName} ${props.node.has_errors ? 'Errored' : ''}`}
onClick={e => props.onRowClick(e, props.index, props.node)}
onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
style={{
top: props.style.top,
height: props.style.height,
}}
>
props.manager.registerColumnRef('list', r, virtualized_index, props.node)
}
>
{' '}
{props.node.children.length > 0 || props.node.canFetch ? (
void 0}>
{props.node.children.length > 0
? COUNT_FORMATTER.format(props.node.children.length)
: null}
) : null}
{t('Trace')}
—
{props.trace_id}
props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
}
className={
props.index % 2 === 1
? RIGHT_COLUMN_ODD_CLASSNAME
: RIGHT_COLUMN_EVEN_CLASSNAME
}
onDoubleClick={e => {
e.stopPropagation();
props.manager.onZoomIntoSpace(props.node.space!);
}}
>
);
}
if (isTraceErrorNode(props.node)) {
return (
props.tabIndex === props.index
? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
: null
}
tabIndex={props.tabIndex === props.index ? 0 : -1}
className={`TraceRow ${rowSearchClassName} Errored`}
onClick={e => props.onRowClick(e, props.index, props.node)}
onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
style={{
top: props.style.top,
height: props.style.height,
}}
>
props.manager.registerColumnRef('list', r, virtualized_index, props.node)
}
>
{' '}
{t('Error')}
—
{props.node.value.title}
props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
}
className={
props.index % 2 === 1
? RIGHT_COLUMN_ODD_CLASSNAME
: RIGHT_COLUMN_EVEN_CLASSNAME
}
onDoubleClick={e => {
e.stopPropagation();
props.manager.onZoomIntoSpace(props.node.space!);
}}
>
{typeof props.node.value.timestamp === 'number' ? (
) : null}
);
}
if (isNoDataNode(props.node)) {
return (
props.tabIndex === props.index
? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
: null
}
tabIndex={props.tabIndex === props.index ? 0 : -1}
className={`TraceRow ${rowSearchClassName}`}
onClick={e => props.onRowClick(e, props.index, props.node)}
onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
style={{
top: props.style.top,
height: props.style.height,
}}
>
props.manager.registerColumnRef('list', r, virtualized_index, props.node)
}
>
{t('Empty')}{' '}
—
{tct('[type] did not report any span data', {
type: props.node.parent
? isTransactionNode(props.node.parent)
? 'Transaction'
: isSpanNode(props.node.parent)
? 'Span'
: ''
: '',
})}
props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
}
className={
props.index % 2 === 1
? RIGHT_COLUMN_ODD_CLASSNAME
: RIGHT_COLUMN_EVEN_CLASSNAME
}
/>
);
}
return null;
}
function RenderPlaceholderRow(props: {
index: number;
manager: VirtualizedViewManager;
node: TraceTreeNode
;
projects: Record;
style: React.CSSProperties;
theme: Theme;
}) {
return (
{props.node.children.length > 0 || props.node.canFetch ? (
void 0}
>
{props.node.children.length > 0
? COUNT_FORMATTER.format(props.node.children.length)
: null}
) : null}
);
}
function randomBetween(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function Connectors(props: {
manager: VirtualizedViewManager;
node: TraceTreeNode;
}) {
const showVerticalConnector =
((props.node.expanded || props.node.zoomedIn) && props.node.children.length > 0) ||
(props.node.value && isParentAutogroupedNode(props.node));
// If the tail node of the collapsed node has no children,
// we don't want to render the vertical connector as no children
// are being rendered as the chain is entirely collapsed
const hideVerticalConnector =
showVerticalConnector &&
props.node.value &&
props.node instanceof ParentAutogroupNode &&
!props.node.tail.children.length;
return (
{props.node.connectors.map((c, i) => {
return (
);
})}
{showVerticalConnector && !hideVerticalConnector ? (
) : null}
{props.node.isLastChild ? (
) : (
)}
);
}
function ChildrenButton(props: {
children: React.ReactNode;
expanded: boolean;
icon: React.ReactNode;
onClick: (e: React.MouseEvent) => void;
status: TraceTreeNode['fetchStatus'] | undefined;
errored?: boolean;
}) {
return (
);
}
interface TraceBarProps {
color: string;
errors: TraceTreeNode['errors'];
manager: VirtualizedViewManager;
node_space: [number, number] | null;
performance_issues: TraceTreeNode['performance_issues'];
profiles: TraceTreeNode['profiles'];
virtualized_index: number;
}
function TraceBar(props: TraceBarProps) {
if (!props.node_space) {
return null;
}
const duration = getDuration(props.node_space[1]);
const spanTransform = props.manager.computeSpanCSSMatrixTransform(props.node_space);
const [inside, textTransform] = props.manager.computeSpanTextPlacement(
props.node_space,
duration
);
return (
props.manager.registerSpanBarRef(r, props.node_space!, props.virtualized_index)
}
className="TraceBar"
style={
{
transform: `matrix(${spanTransform.join(',')})`,
'--inverse-span-scale': 1 / spanTransform[0],
backgroundColor: props.color,
// unknown css variables cannot be part of the style object
} as React.CSSProperties
}
>
{props.profiles.length > 0 ? (
) : null}
{props.errors.size > 0 ? (
) : null}
{props.performance_issues.size > 0 ? (
) : null}
props.manager.registerSpanBarTextRef(
r,
duration,
props.node_space!,
props.virtualized_index
)
}
className="TraceBarDuration"
style={{
color: inside ? 'white' : '',
transform: `translate(${textTransform ?? 0}px, 0)`,
}}
>
{duration}
);
}
interface InvisibleTraceBarProps {
children: React.ReactNode;
manager: VirtualizedViewManager;
node_space: [number, number] | null;
virtualizedIndex: number;
}
function InvisibleTraceBar(props: InvisibleTraceBarProps) {
if (!props.node_space || !props.children) {
return null;
}
const spanTransform = `translateX(${props.manager.computeTransformXFromTimestamp(props.node_space[0])}px)`;
return (
props.manager.registerInvisibleBarRef(
r,
props.node_space!,
props.virtualizedIndex
)
}
className="TraceBar Invisible"
style={
{
transform: spanTransform,
// undefined css variables break style rules
'--inverse-span-scale': 1,
// unknown css variables cannot be part of the style object
} as React.CSSProperties
}
onDoubleClick={e => {
e.stopPropagation();
props.manager.onZoomIntoSpace(props.node_space!);
}}
>
{props.children}
);
}
interface PerformanceIssuesProps {
manager: VirtualizedViewManager;
node_space: [number, number] | null;
performance_issues: TraceTreeNode['performance_issues'];
}
function PerformanceIssues(props: PerformanceIssuesProps) {
const performance_issues = useMemo(() => {
return [...props.performance_issues];
}, [props.performance_issues]);
if (!props.performance_issues.size) {
return null;
}
return (
{performance_issues.map((issue, _i) => {
const timestamp = issue.start * 1e3;
// Clamp the issue timestamp to the span's timestamp
const left = props.manager.computeRelativeLeftPositionFromOrigin(
clamp(
timestamp,
props.node_space![0],
props.node_space![0] + props.node_space![1]
),
props.node_space!
);
const max_width = 100 - left;
const issue_duration = (issue.end - issue.start) * 1e3;
const width = clamp((issue_duration / props.node_space![1]) * 100, 0, max_width);
return (
);
})}
);
}
interface ErrorsProps {
errors: TraceTreeNode['errors'];
manager: VirtualizedViewManager;
node_space: [number, number] | null;
}
function Errors(props: ErrorsProps) {
const errors = useMemo(() => {
return [...props.errors];
}, [props.errors]);
if (!props.errors.size) {
return null;
}
return (
{errors.map((error, _i) => {
const timestamp = error.timestamp ? error.timestamp * 1e3 : props.node_space![0];
// Clamp the error timestamp to the span's timestamp
const left = props.manager.computeRelativeLeftPositionFromOrigin(
clamp(
timestamp,
props.node_space![0],
props.node_space![0] + props.node_space![1]
),
props.node_space!
);
return (
);
})}
);
}
interface ProfilesProps {
manager: VirtualizedViewManager;
node_space: [number, number] | null;
profiles: TraceTree.Profile[];
}
function Profiles(props: ProfilesProps) {
if (!props.profiles.length) {
return null;
}
return (
{props.profiles.map((profile, _i) => {
const timestamp = profile.space[0];
// Clamp the profile timestamp to the span's timestamp
const left = props.manager.computeRelativeLeftPositionFromOrigin(
clamp(
timestamp,
props.node_space![0],
props.node_space![0] + props.node_space![1]
),
props.node_space!
);
return (
);
})}
);
}
interface AutogroupedTraceBarProps {
color: string;
entire_space: [number, number] | null;
errors: TraceTreeNode['errors'];
manager: VirtualizedViewManager;
node_spaces: [number, number][];
performance_issues: TraceTreeNode['performance_issues'];
profiles: TraceTreeNode['profiles'];
virtualized_index: number;
}
function AutogroupedTraceBar(props: AutogroupedTraceBarProps) {
if (props.node_spaces && props.node_spaces.length <= 1) {
return (
);
}
if (!props.node_spaces || !props.entire_space) {
return null;
}
const duration = getDuration(props.entire_space[1]);
const spanTransform = props.manager.computeSpanCSSMatrixTransform(props.entire_space);
const [inside, textTransform] = props.manager.computeSpanTextPlacement(
props.entire_space,
duration
);
return (
props.manager.registerSpanBarRef(
r,
props.entire_space!,
props.virtualized_index
)
}
className="TraceBar Invisible"
style={{
transform: `matrix(${spanTransform.join(',')})`,
backgroundColor: props.color,
}}
>
{props.node_spaces.map((node_space, i) => {
const width = node_space[1] / props.entire_space![1];
const left = props.manager.computeRelativeLeftPositionFromOrigin(
node_space[0],
props.entire_space!
);
return (
);
})}
{props.profiles.length > 0 ? (
) : null}
{props.errors.size > 0 ? (
) : null}
{props.performance_issues.size > 0 ? (
) : null}
props.manager.registerSpanBarTextRef(
r,
duration,
props.entire_space!,
props.virtualized_index
)
}
className="TraceBarDuration"
style={{
color: inside ? 'white' : '',
transform: `translate(${textTransform ?? 0}px, 0)`,
}}
>
{duration}
);
}
/**
* This is a wrapper around the Trace component to apply styles
* to the trace tree. It exists because we _do not_ want to trigger
* emotion's css parsing logic as it is very slow and will cause
* the scrolling to flicker.
*/
const TraceStylingWrapper = styled('div')`
margin: auto;
overscroll-behavior: none;
box-shadow: 0 0 0 1px ${p => p.theme.border};
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
grid-area: trace;
padding-top: 26px;
&.WithIndicators {
padding-top: 44px;
&:before {
height: 44px;
.TraceScrollbarContainer {
height: 44px;
}
}
.TraceIndicator.Timeline {
.TraceIndicatorLabel {
top: 26px;
}
.TraceIndicatorLine {
top: 30px;
}
}
}
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 26px;
background-color: ${p => p.theme.backgroundSecondary};
border-bottom: 1px solid ${p => p.theme.border};
}
&.Loading {
.TraceRow {
.TraceLeftColumnInner {
width: 100%;
}
}
.TraceRightColumn {
background-color: transparent !important;
}
.TraceDivider {
pointer-events: none;
}
}
.TraceScrollbarContainer {
height: 26px;
position: absolute;
left: 0;
top: 0;
overflow: auto;
overscroll-behavior: none;
.TraceScrollbarScroller {
height: 1px;
pointer-events: none;
visibility: hidden;
}
.TraceScrollbarHandle {
width: 24px;
height: 12px;
border-radius: 6px;
}
}
.TraceDivider {
position: absolute;
height: 100%;
background-color: transparent;
top: 0;
cursor: col-resize;
z-index: 10;
transform: translateX(calc(var(--translate-x) * 1px));
&:before {
content: '';
position: absolute;
width: 1px;
height: 100%;
background-color: ${p => p.theme.border};
left: 50%;
}
&:hover {
&:before {
background-color: ${p => p.theme.purple300};
}
}
}
.TraceIndicatorContainer {
overflow: hidden;
width: 100%;
height: 100%;
position: absolute;
right: 0;
top: 0;
transform: translateX(calc(var(--translate-x) * 1px));
}
.TraceIndicator {
z-index: 1;
width: 3px;
height: 100%;
top: 0;
position: absolute;
.TraceIndicatorLabel {
min-width: 34px;
text-align: center;
position: absolute;
font-size: 10px;
font-weight: bold;
color: ${p => p.theme.textColor};
background-color: ${p => p.theme.background};
border-radius: ${p => p.theme.borderRadius};
border: 1px solid ${p => p.theme.border};
padding: 2px;
display: inline-block;
line-height: 1;
margin-top: 2px;
white-space: nowrap;
}
.TraceIndicatorLine {
width: 1px;
height: 100%;
top: 20px;
position: absolute;
left: 50%;
transform: translateX(-2px);
background: repeating-linear-gradient(
to bottom,
transparent 0 4px,
${p => p.theme.textColor} 4px 8px
)
80%/2px 100% no-repeat;
}
&.Errored {
.TraceIndicatorLabel {
border: 1px solid ${p => p.theme.error};
color: ${p => p.theme.error};
}
.TraceIndicatorLine {
background: repeating-linear-gradient(
to bottom,
transparent 0 4px,
${p => p.theme.error} 4px 8px
)
80%/2px 100% no-repeat;
}
}
&.Timeline {
opacity: 1;
z-index: 1;
pointer-events: none;
.TraceIndicatorLabel {
font-weight: normal;
min-width: 0;
top: 8px;
width: auto;
border: none;
background-color: transparent;
color: ${p => p.theme.subText};
}
.TraceIndicatorLine {
background: ${p => p.theme.translucentGray100};
top: 8px;
}
}
}
.TraceRow {
display: flex;
align-items: center;
position: absolute;
height: 24px;
width: 100%;
transition: none;
font-size: ${p => p.theme.fontSizeSmall};
--row-background-odd: ${p => p.theme.translucentSurface100};
--row-background-hover: ${p => p.theme.translucentSurface100};
--row-background-focused: ${p => p.theme.translucentSurface200};
&.Hidden {
position: absolute;
height: 100%;
width: 100%;
top: 0;
z-index: -1;
&:hover {
background-color: transparent;
}
* {
cursor: default !important;
}
}
.TraceError {
position: absolute;
top: 50%;
transform: translate(-50%, -50%) scaleX(var(--inverse-span-scale));
background: ${p => p.theme.background};
width: 18px !important;
height: 18px !important;
background-color: ${p => p.theme.error};
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
svg {
fill: ${p => p.theme.white};
}
}
.TraceProfile {
position: absolute;
top: 50%;
transform: translate(-50%, -50%) scaleX(var(--inverse-span-scale));
background: ${p => p.theme.background};
width: 18px !important;
height: 18px !important;
background-color: ${p => p.theme.purple300};
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
svg {
width: 12px;
height: 12px;
margin-left: 2px;
fill: ${p => p.theme.white};
}
}
.TracePerformanceIssue {
position: absolute;
top: 0;
display: flex;
align-items: center;
justify-content: flex-start;
background-color: ${p => p.theme.error};
height: 16px;
}
.TraceRightColumn.Odd {
background-color: var(--row-background-odd);
}
&:hover {
background-color: var(--row-background-hovered);
}
&.Highlight {
box-shadow: inset 0 0 0 1px ${p => p.theme.blue200} !important;
.TraceLeftColumn {
box-shadow: inset 0px 0 0px 1px ${p => p.theme.blue200} !important;
}
}
&.Highlight,
&:focus {
outline: none;
background-color: var(--row-background-focused);
.TraceRightColumn.Odd {
background-color: transparent !important;
}
}
&:focus,
&[tabindex='0'] {
background-color: var(--row-background-focused);
box-shadow: inset 0 0 0 1px ${p => p.theme.blue300} !important;
.TraceLeftColumn {
box-shadow: inset 0px 0 0px 1px ${p => p.theme.blue300} !important;
}
.TraceRightColumn.Odd {
background-color: transparent !important;
}
}
&.Errored {
color: ${p => p.theme.error};
.TraceChildrenCount {
border: 2px solid ${p => p.theme.error};
svg {
fill: ${p => p.theme.error};
}
}
&:focus,
&[tabindex='0'] {
box-shadow: inset 0 0 0 1px ${p => p.theme.red300} !important;
.TraceLeftColumn {
box-shadow: inset 0px 0 0px 1px ${p => p.theme.red300} !important;
}
}
}
&.SearchResult {
background-color: ${p => p.theme.yellow100};
.TraceRightColumn {
background-color: transparent;
}
}
&.Autogrouped {
color: ${p => p.theme.blue300};
&.Errored {
.TraceChildrenCount {
background-color: ${p => p.theme.error} !important;
}
}
.TraceDescription {
font-weight: bold;
}
.TraceChildrenCountWrapper {
button {
color: ${p => p.theme.white};
background-color: ${p => p.theme.blue300};
}
svg {
fill: ${p => p.theme.white};
}
}
}
}
.TraceLeftColumn {
height: 100%;
white-space: nowrap;
display: flex;
align-items: center;
overflow: hidden;
will-change: width;
box-shadow: inset 1px 0 0px 0px transparent;
cursor: pointer;
width: calc(var(--list-column-width) * 100%);
.TraceLeftColumnInner {
height: 100%;
white-space: nowrap;
display: flex;
align-items: center;
will-change: transform;
transform-origin: left center;
padding-right: ${space(2)};
img {
width: 16px;
height: 16px;
}
}
}
.TraceRightColumn {
height: 100%;
overflow: hidden;
position: relative;
display: flex;
align-items: center;
will-change: width;
z-index: 1;
cursor: pointer;
width: calc(var(--span-column-width) * 100%);
&:hover {
.TraceArrow.Visible {
opacity: 1;
transition: 300ms 300ms ease-out;
pointer-events: auto;
}
}
}
.TraceBar {
position: absolute;
height: 16px;
width: 100%;
background-color: black;
transform-origin: left center;
&.Invisible {
background-color: transparent !important;
> div {
height: 100%;
}
}
svg {
width: 14px;
height: 14px;
}
}
.TraceArrow {
position: absolute;
pointer-events: none;
top: 0;
width: 14px;
height: 24px;
opacity: 0;
background-color: transparent;
border: none;
transition: 60ms ease-out;
font-size: ${p => p.theme.fontSizeMedium};
color: ${p => p.theme.subText};
padding: 0 2px;
display: flex;
align-items: center;
svg {
fill: ${p => p.theme.subText};
}
&.Left {
left: 0;
}
&.Right {
right: 0;
transform: rotate(180deg);
}
}
.TraceBarDuration {
display: inline-block;
transform-origin: left center;
font-size: ${p => p.theme.fontSizeExtraSmall};
color: ${p => p.theme.gray300};
white-space: nowrap;
font-variant-numeric: tabular-nums;
position: absolute;
transition: color 0.1s ease-in-out;
}
.TraceChildrenCount {
height: 16px;
white-space: nowrap;
min-width: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 99px;
padding: 0px 4px;
transition: all 0.15s ease-in-out;
background: ${p => p.theme.background};
border: 2px solid ${p => p.theme.border};
line-height: 0;
z-index: 1;
font-size: 10px;
box-shadow: ${p => p.theme.dropShadowLight};
margin-right: 8px;
.TraceChildrenCountContent {
+ .TraceChildrenCountAction {
margin-left: 2px;
}
}
.TraceChildrenCountAction {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.TraceActionsLoadingIndicator {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: ${p => p.theme.background};
animation: show 0.1s ease-in-out forwards;
@keyframes show {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.86);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
.loading-indicator {
border-width: 2px;
}
.loading-message {
display: none;
}
}
svg {
width: 7px;
transition: none;
fill: ${p => p.theme.textColor};
}
}
.TraceChildrenCountWrapper {
display: flex;
justify-content: flex-end;
align-items: center;
min-width: 44px;
height: 100%;
position: relative;
button {
transition: none;
}
&.Orphaned {
.TraceVerticalConnector,
.TraceVerticalLastChildConnector,
.TraceExpandedVerticalConnector {
border-left: 2px dashed ${p => p.theme.border};
}
&::before {
border-bottom: 2px dashed ${p => p.theme.border};
}
}
&.Root {
&:before,
.TraceVerticalLastChildConnector {
visibility: hidden;
}
}
&::before {
content: '';
display: block;
width: 50%;
height: 2px;
border-bottom: 2px solid ${p => p.theme.border};
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
&::after {
content: '';
background-color: ${p => p.theme.border};
border-radius: 50%;
height: 6px;
width: 6px;
position: absolute;
left: 50%;
top: 50%;
transform: translateY(-50%);
}
}
.TraceVerticalConnector {
position: absolute;
left: 0;
top: 0;
bottom: 0;
height: 100%;
width: 2px;
border-left: 2px solid ${p => p.theme.border};
&.Orphaned {
border-left: 2px dashed ${p => p.theme.border};
}
}
.TraceVerticalLastChildConnector {
position: absolute;
left: 0;
top: 0;
bottom: 0;
height: 50%;
width: 2px;
border-left: 2px solid ${p => p.theme.border};
border-bottom-left-radius: 4px;
}
.TraceExpandedVerticalConnector {
position: absolute;
bottom: 0;
height: 50%;
left: 50%;
width: 2px;
border-left: 2px solid ${p => p.theme.border};
}
.TraceOperation {
margin-left: 4px;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
}
.TraceEmDash {
margin-left: 4px;
margin-right: 4px;
}
.TraceDescription {
white-space: nowrap;
}
`;