123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969 |
- import {Fragment, useCallback, useMemo, useRef, useState} from 'react';
- import {AutoSizer, List} from 'react-virtualized';
- import {type Theme, useTheme} from '@emotion/react';
- import styled from '@emotion/styled';
- import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
- import LoadingIndicator from 'sentry/components/loadingIndicator';
- import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
- import Placeholder from 'sentry/components/placeholder';
- import {IconChevron} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {Project} from 'sentry/types';
- import type {
- TraceFullDetailed,
- TraceSplitResults,
- } from 'sentry/utils/performance/quickTrace/types';
- import useApi from 'sentry/utils/useApi';
- import useOrganization from 'sentry/utils/useOrganization';
- import useProjects from 'sentry/utils/useProjects';
- import {
- isAutogroupedNode,
- isMissingInstrumentationNode,
- isParentAutogroupedNode,
- isSpanNode,
- isTraceErrorNode,
- isTraceNode,
- isTransactionNode,
- } from './guards';
- import {ParentAutogroupNode, TraceTree, type TraceTreeNode} from './traceTree';
- import {VirtualizedViewManager} from './virtualizedViewManager';
- interface TraceProps {
- trace: TraceSplitResults<TraceFullDetailed> | null;
- trace_id: string;
- }
- export function Trace(props: TraceProps) {
- const theme = useTheme();
- const api = useApi();
- const {projects} = useProjects();
- const organization = useOrganization();
- const virtualizedListRef = useRef<List>(null);
- const viewManager = useRef<VirtualizedViewManager | null>(null);
- const [_rerender, setRender] = useState(0);
- const traceTree = useMemo(() => {
- if (!props.trace) {
- return TraceTree.Loading({
- project_slug: projects?.[0]?.slug ?? '',
- event_id: props.trace_id,
- });
- }
- return TraceTree.FromTrace(props.trace);
- }, [props.trace, props.trace_id, projects]);
- if (!viewManager.current) {
- viewManager.current = new VirtualizedViewManager({
- list: {width: 0.5, column_refs: []},
- span_list: {width: 0.5, column_refs: []},
- });
- }
- if (
- traceTree.root.space &&
- (traceTree.root.space[0] !== viewManager.current.spanSpace[0] ||
- traceTree.root.space[1] !== viewManager.current.spanSpace[1])
- ) {
- viewManager.current.initializeSpanSpace(traceTree.root.space);
- }
- const treeRef = useRef<TraceTree>(traceTree);
- treeRef.current = traceTree;
- const handleFetchChildren = useCallback(
- (node: TraceTreeNode<TraceTree.NodeValue>, value: boolean) => {
- treeRef.current
- .zoomIn(node, value, {
- api,
- organization,
- })
- .then(() => {
- setRender(a => (a + 1) % 2);
- });
- },
- [api, organization]
- );
- const handleExpandNode = useCallback(
- (node: TraceTreeNode<TraceTree.NodeValue>, value: boolean) => {
- treeRef.current.expand(node, value);
- setRender(a => (a + 1) % 2);
- },
- []
- );
- const projectLookup = useMemo(() => {
- return projects.reduce<Record<Project['slug'], Project>>((acc, project) => {
- acc[project.slug] = project;
- return acc;
- }, {});
- }, [projects]);
- return (
- <Fragment>
- <TraceStylingWrapper
- ref={r => viewManager.current?.onContainerRef(r)}
- className={traceTree.type === 'loading' ? 'Loading' : ''}
- style={{
- backgroundColor: '#FFF',
- height: '100%',
- width: '100%',
- position: 'absolute',
- }}
- >
- <TraceDivider ref={r => viewManager.current?.registerDividerRef(r)} />
- <AutoSizer>
- {({width, height}) => (
- <List
- ref={virtualizedListRef}
- rowHeight={24}
- height={height}
- width={width}
- overscanRowCount={10}
- rowCount={treeRef.current.list.length ?? 0}
- rowRenderer={p => {
- return traceTree.type === 'loading' ? (
- <RenderPlaceholderRow
- style={p.style}
- node={treeRef.current.list[p.index]}
- index={p.index}
- theme={theme}
- projects={projectLookup}
- viewManager={viewManager.current!}
- startIndex={
- (p.parent as unknown as {_rowStartIndex: number})._rowStartIndex
- }
- />
- ) : (
- <RenderRow
- key={p.key}
- theme={theme}
- startIndex={
- (p.parent as unknown as {_rowStartIndex: number})._rowStartIndex
- }
- index={p.index}
- style={p.style}
- trace_id={props.trace_id}
- projects={projectLookup}
- node={treeRef.current.list[p.index]}
- viewManager={viewManager.current!}
- onFetchChildren={handleFetchChildren}
- onExpandNode={handleExpandNode}
- />
- );
- }}
- />
- )}
- </AutoSizer>
- </TraceStylingWrapper>
- </Fragment>
- );
- }
- const TraceDivider = styled('div')`
- position: absolute;
- height: 100%;
- background-color: transparent;
- top: 0;
- z-index: 1;
- cursor: col-resize;
- &:before {
- content: '';
- position: absolute;
- width: 1px;
- height: 100%;
- background-color: ${p => p.theme.border};
- left: 50%;
- }
- &:hover&:before {
- background-color: ${p => p.theme.purple300};
- }
- `;
- function RenderRow(props: {
- index: number;
- node: TraceTreeNode<TraceTree.NodeValue>;
- onExpandNode: (node: TraceTreeNode<TraceTree.NodeValue>, value: boolean) => void;
- onFetchChildren: (node: TraceTreeNode<TraceTree.NodeValue>, value: boolean) => void;
- projects: Record<Project['slug'], Project>;
- startIndex: number;
- style: React.CSSProperties;
- theme: Theme;
- trace_id: string;
- viewManager: VirtualizedViewManager;
- }) {
- const virtualizedIndex = props.index - props.startIndex;
- if (!props.node.value) {
- return null;
- }
- if (isAutogroupedNode(props.node)) {
- return (
- <div
- className="TraceRow Autogrouped"
- style={{
- top: props.style.top,
- height: props.style.height,
- }}
- >
- <div
- className="TraceLeftColumn"
- ref={r => props.viewManager.registerColumnRef('list', r, virtualizedIndex)}
- style={{
- width: props.viewManager.columns.list.width * 100 + '%',
- }}
- >
- <div
- className="TraceLeftColumnInner"
- style={{paddingLeft: props.node.depth * 23}}
- >
- <div className="TraceChildrenCountWrapper">
- <Connectors node={props.node} />
- <ChildrenCountButton
- expanded={!props.node.expanded}
- onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
- >
- {props.node.groupCount}{' '}
- </ChildrenCountButton>
- </div>
- <span className="TraceOperation">{t('Autogrouped')}</span>
- <strong className="TraceEmDash"> — </strong>
- <span className="TraceDescription">{props.node.value.autogrouped_by.op}</span>
- </div>
- </div>
- <div
- className="TraceRightColumn"
- ref={r => props.viewManager.registerColumnRef('span_list', r, virtualizedIndex)}
- style={{
- width: props.viewManager.columns.span_list.width * 100 + '%',
- backgroundColor:
- props.index % 2 ? undefined : props.theme.backgroundSecondary,
- }}
- >
- <TraceBar
- virtualizedIndex={virtualizedIndex}
- viewManager={props.viewManager}
- color={pickBarColor('autogrouping')}
- node_space={props.node.space}
- />
- </div>
- </div>
- );
- }
- if (isTransactionNode(props.node)) {
- return (
- <div
- className="TraceRow"
- style={{
- top: props.style.top,
- height: props.style.height,
- }}
- >
- <div
- className="TraceLeftColumn"
- ref={r => props.viewManager.registerColumnRef('list', r, virtualizedIndex)}
- style={{
- width: props.viewManager.columns.list.width * 100 + '%',
- }}
- >
- <div
- className="TraceLeftColumnInner"
- style={{paddingLeft: props.node.depth * 23}}
- >
- <div
- className={`TraceChildrenCountWrapper ${
- props.node.isOrphaned ? 'Orphaned' : ''
- }`}
- >
- <Connectors node={props.node} />
- {props.node.children.length > 0 ? (
- <ChildrenCountButton
- expanded={props.node.expanded || props.node.zoomedIn}
- onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
- >
- {props.node.children.length}{' '}
- </ChildrenCountButton>
- ) : null}
- </div>
- <ProjectBadge project={props.projects[props.node.value.project_slug]} />
- <span className="TraceOperation">{props.node.value['transaction.op']}</span>
- <strong className="TraceEmDash"> — </strong>
- <span>{props.node.value.transaction}</span>
- {props.node.canFetchData ? (
- <button
- onClick={() => props.onFetchChildren(props.node, !props.node.zoomedIn)}
- >
- {props.node.zoomedIn ? 'Zoom Out' : 'Zoom In'}
- </button>
- ) : null}
- </div>
- </div>
- <div
- ref={r => props.viewManager.registerColumnRef('span_list', r, virtualizedIndex)}
- className="TraceRightColumn"
- style={{
- width: props.viewManager.columns.span_list.width * 100 + '%',
- backgroundColor:
- props.index % 2 ? undefined : props.theme.backgroundSecondary,
- }}
- >
- <TraceBar
- virtualizedIndex={virtualizedIndex}
- viewManager={props.viewManager}
- color={pickBarColor(props.node.value['transaction.op'])}
- node_space={props.node.space}
- />
- </div>
- </div>
- );
- }
- if (isSpanNode(props.node)) {
- return (
- <div
- className="TraceRow"
- style={{
- top: props.style.top,
- height: props.style.height,
- }}
- >
- <div
- className="TraceLeftColumn"
- ref={r => props.viewManager.registerColumnRef('list', r, virtualizedIndex)}
- style={{
- width: props.viewManager.columns.list.width * 100 + '%',
- }}
- >
- <div
- className="TraceLeftColumnInner"
- style={{paddingLeft: props.node.depth * 23}}
- >
- <div
- className={`TraceChildrenCountWrapper ${
- props.node.isOrphaned ? 'Orphaned' : ''
- }`}
- >
- <Connectors node={props.node} />
- {props.node.children.length > 0 ? (
- <ChildrenCountButton
- expanded={props.node.expanded || props.node.zoomedIn}
- onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
- >
- {props.node.children.length}{' '}
- </ChildrenCountButton>
- ) : null}
- </div>
- <span className="TraceOperation">{props.node.value.op ?? '<unknown>'}</span>
- <strong className="TraceEmDash"> — </strong>
- <span className="TraceDescription">
- {props.node.value.description ?? '<unknown>'}
- </span>
- {props.node.canFetchData ? (
- <button
- onClick={() => props.onFetchChildren(props.node, !props.node.zoomedIn)}
- >
- {props.node.zoomedIn ? 'Zoom Out' : 'Zoom In'}
- </button>
- ) : null}
- </div>
- </div>
- <div
- ref={r => props.viewManager.registerColumnRef('span_list', r, virtualizedIndex)}
- className="TraceRightColumn"
- style={{
- width: props.viewManager.columns.span_list.width * 100 + '%',
- backgroundColor:
- props.index % 2 ? undefined : props.theme.backgroundSecondary,
- }}
- >
- <TraceBar
- virtualizedIndex={virtualizedIndex}
- viewManager={props.viewManager}
- color={pickBarColor(props.node.value.op)}
- node_space={props.node.space}
- />
- </div>
- </div>
- );
- }
- if (isMissingInstrumentationNode(props.node)) {
- return (
- <div
- className="TraceRow"
- style={{
- top: props.style.top,
- height: props.style.height,
- }}
- >
- <div
- className="TraceLeftColumn"
- ref={r => props.viewManager.registerColumnRef('list', r, virtualizedIndex)}
- style={{
- width: props.viewManager.columns.list.width * 100 + '%',
- }}
- >
- <div
- className="TraceLeftColumnInner"
- style={{paddingLeft: props.node.depth * 23}}
- >
- <div className="TraceChildrenCountWrapper">
- <Connectors node={props.node} />
- </div>
- <span className="TraceOperation">{t('Missing instrumentation')}</span>
- </div>
- </div>
- <div
- ref={r => props.viewManager.registerColumnRef('span_list', r, virtualizedIndex)}
- className="TraceRightColumn"
- style={{
- width: props.viewManager.columns.span_list.width * 100 + '%',
- backgroundColor:
- props.index % 2 ? undefined : props.theme.backgroundSecondary,
- }}
- >
- <TraceBar
- virtualizedIndex={virtualizedIndex}
- viewManager={props.viewManager}
- color={pickBarColor('missing-instrumentation')}
- node_space={props.node.space}
- />
- </div>
- </div>
- );
- }
- if (isTraceNode(props.node)) {
- return (
- <div
- className="TraceRow"
- style={{
- top: props.style.top,
- height: props.style.height,
- }}
- >
- <div
- className="TraceLeftColumn"
- ref={r => props.viewManager.registerColumnRef('list', r, virtualizedIndex)}
- style={{
- width: props.viewManager.columns.list.width * 100 + '%',
- }}
- >
- <div
- className="TraceLeftColumnInner"
- style={{paddingLeft: props.node.depth * 23}}
- >
- <div className="TraceChildrenCountWrapper Root">
- <Connectors node={props.node} />
- {props.node.children.length > 0 ? (
- <ChildrenCountButton
- expanded={props.node.expanded || props.node.zoomedIn}
- onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
- >
- {props.node.children.length}{' '}
- </ChildrenCountButton>
- ) : null}
- </div>
- <span className="TraceOperation">{t('Trace')}</span>
- <strong className="TraceEmDash"> — </strong>
- <span className="TraceDescription">{props.trace_id}</span>
- </div>
- </div>
- <div
- ref={r => props.viewManager.registerColumnRef('span_list', r, virtualizedIndex)}
- className="TraceRightColumn"
- style={{
- width: props.viewManager.columns.span_list.width * 100 + '%',
- backgroundColor:
- props.index % 2 ? undefined : props.theme.backgroundSecondary,
- }}
- >
- <TraceBar
- virtualizedIndex={virtualizedIndex}
- viewManager={props.viewManager}
- color={pickBarColor('missing-instrumentation')}
- node_space={props.node.space}
- />
- </div>
- </div>
- );
- }
- if (isTraceErrorNode(props.node)) {
- <div
- className="TraceRow"
- style={{
- top: props.style.top,
- height: props.style.height,
- }}
- >
- <div
- className="TraceLeftColumn"
- ref={r => props.viewManager.registerColumnRef('list', r, virtualizedIndex)}
- style={{
- width:
- (props.viewManager.columns.list.width / props.viewManager.width) * 100 + '%',
- }}
- >
- <div
- className="TraceLeftColumnInner"
- style={{paddingLeft: props.node.depth * 23}}
- >
- <div className="TraceChildrenCountWrapper">
- <Connectors node={props.node} />
- {props.node.children.length > 0 ? (
- <ChildrenCountButton
- expanded={props.node.expanded || props.node.zoomedIn}
- onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
- >
- {props.node.children.length}{' '}
- </ChildrenCountButton>
- ) : null}
- </div>
- <span className="TraceOperation">{t('Error')}</span>
- <strong className="TraceEmDash"> — </strong>
- <span className="TraceDescription">{props.node.value.title}</span>
- </div>
- </div>
- <div
- ref={r => props.viewManager.registerColumnRef('span_list', r, virtualizedIndex)}
- className="TraceRightColumn"
- style={{
- width: props.viewManager.columns.span_list.width * 100 + '%',
- backgroundColor: props.index % 2 ? undefined : props.theme.backgroundSecondary,
- }}
- >
- {/* @TODO: figure out what to do with trace errors */}
- {/* <TraceBar
- space={props.space}
- start_timestamp={props.node.value.start_timestamp}
- timestamp={props.node.value.timestamp}
- /> */}
- </div>
- </div>;
- }
- return null;
- }
- function RenderPlaceholderRow(props: {
- index: number;
- node: TraceTreeNode<TraceTree.NodeValue>;
- projects: Record<Project['slug'], Project>;
- startIndex: number;
- style: React.CSSProperties;
- theme: Theme;
- viewManager: VirtualizedViewManager;
- }) {
- const virtualizedIndex = props.index - props.startIndex;
- return (
- <div
- className="TraceRow"
- style={{
- top: props.style.top,
- height: props.style.height,
- pointerEvents: 'none',
- color: props.theme.subText,
- animationDelay: `${virtualizedIndex * 0.05}s`,
- }}
- >
- <div
- className="TraceLeftColumn"
- ref={r => props.viewManager.registerColumnRef('list', r, virtualizedIndex)}
- style={{width: props.viewManager.columns.list.width * 100 + '%'}}
- >
- <div
- className="TraceLeftColumnInner"
- style={{paddingLeft: props.node.depth * 23}}
- >
- <div className="TraceChildrenCountWrapper">
- <Connectors node={props.node} />
- {props.node.children.length > 0 ? (
- <ChildrenCountButton
- expanded={props.node.expanded || props.node.zoomedIn}
- onClick={() => void 0}
- >
- {props.node.children.length}{' '}
- </ChildrenCountButton>
- ) : null}
- </div>
- {isTraceNode(props.node) ? <SmallLoadingIndicator /> : null}
- {isTraceNode(props.node) ? (
- 'Loading trace...'
- ) : (
- <Placeholder className="Placeholder" height="10px" width="86%" />
- )}
- </div>
- </div>
- <div
- className="TraceRightColumn"
- ref={r => props.viewManager.registerColumnRef('span_list', r, virtualizedIndex)}
- style={{
- width: props.viewManager.columns.span_list.width * 100 + '%',
- }}
- >
- {isTraceNode(props.node) ? null : (
- <Placeholder
- className="Placeholder"
- height="14px"
- width="90%"
- style={{margin: 'auto'}}
- />
- )}
- </div>
- </div>
- );
- }
- function Connectors(props: {node: TraceTreeNode<TraceTree.NodeValue>}) {
- 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 (
- <Fragment>
- {/*
- @TODO count of rendered connectors could be % 3 as we can
- have up to 3 connectors per node, 1 div, 1 before and 1 after
- */}
- {props.node.connectors.map((c, i) => {
- return (
- <div
- key={i}
- style={{left: -(Math.abs(Math.abs(c) - props.node.depth) * 23)}}
- className={`TraceVerticalConnector ${c < 0 ? 'Orphaned' : ''}`}
- />
- );
- })}
- {showVerticalConnector && !hideVerticalConnector ? (
- <div className="TraceExpandedVerticalConnector" />
- ) : null}
- {props.node.isLastChild ? (
- <div className="TraceVerticalLastChildConnector" />
- ) : (
- <div className="TraceVerticalConnector" />
- )}
- </Fragment>
- );
- }
- function SmallLoadingIndicator() {
- return (
- <StyledLoadingIndicator
- style={{display: 'inline-block', margin: 0}}
- size={8}
- hideMessage
- relative
- />
- );
- }
- const StyledLoadingIndicator = styled(LoadingIndicator)`
- transform: translate(-5px, 0);
- div:first-child {
- border-left: 6px solid ${p => p.theme.gray300};
- animation: loading 900ms infinite linear;
- }
- `;
- function ProjectBadge(props: {project: Project}) {
- return <ProjectAvatar project={props.project} />;
- }
- function ChildrenCountButton(props: {
- children: React.ReactNode;
- expanded: boolean;
- onClick: () => void;
- }) {
- return (
- <button className="TraceChildrenCount" onClick={props.onClick}>
- {props.children}
- <IconChevron
- size="xs"
- direction={props.expanded ? 'up' : 'down'}
- style={{marginLeft: 2}}
- />
- </button>
- );
- }
- interface TraceBarProps {
- color: string;
- node_space: [number, number] | null;
- viewManager: VirtualizedViewManager;
- virtualizedIndex: number;
- }
- function TraceBar(props: TraceBarProps) {
- if (!props.node_space) {
- return null;
- }
- return (
- <div
- ref={r =>
- props.viewManager.registerSpanBarRef(r, props.node_space!, props.virtualizedIndex)
- }
- className="TraceBar"
- style={{
- position: 'absolute',
- transform: props.viewManager.computeSpanMatrixTransform(props.node_space),
- backgroundColor: props.color,
- }}
- />
- );
- }
- /**
- * 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')`
- position: relative;
- @keyframes show {
- 0% {
- opacity: 0;
- transform: translate(0, 2px);
- }
- 100% {
- opacity: .7;
- transform: translate(0, 0px);
- }
- };
- @keyframes showPlaceholder {
- 0% {
- opacity: 0;
- transform: translate(-8px, 0px);
- }
- 100% {
- opacity: .7;
- transform: translate(0, 0px);
- }
- };
- &.Loading {
- .TraceRow {
- opacity: 0;
- animation: show 0.2s ease-in-out forwards;
- }
- .Placeholder {
- opacity: 0;
- transform: translate(-8px, 0px);
- animation: showPlaceholder 0.2s ease-in-out forwards;
- }
- }
- .TraceRow {
- display: flex;
- align-items: center;
- position: absolute;
- width: 100%;
- transition: background-color 0.15s ease-in-out 0s;
- font-size: ${p => p.theme.fontSizeSmall};
- &:hover {
- background-color: ${p => p.theme.backgroundSecondary};
- }
- &.Autogrouped {
- color: ${p => p.theme.blue300};
- .TraceDescription {
- font-weight: bold;
- }
- .TraceChildrenCountWrapper {
- button {
- color: ${p => p.theme.white};
- background-color: ${p => p.theme.blue300};
- }
- }
- }
- }
- .TraceLeftColumn {
- height: 100%;
- white-space: nowrap;
- display: flex;
- align-items: center;
- overflow: hidden;
- will-change: width;
- .TraceLeftColumnInner {
- width: 100%;
- height: 100%;
- white-space: nowrap;
- display: flex;
- align-items: center;
- }
- }
- .TraceRightColumn {
- height: 100%;
- position: relative;
- display: flex;
- align-items: center;
- will-change: width;
- }
- .TraceBar {
- height: 64%;
- width: 100%;
- background-color: black;
- transform-origin: left center;
- }
- .TraceChildrenCount {
- height: 16px;
- white-space: nowrap;
- min-width: 30px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 99px;
- padding: 0px ${space(0.5)};
- 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: ${space(1)};
- svg {
- width: 7px;
- transition: none;
- }
- }
- .TraceChildrenCountWrapper {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- min-width: 46px;
- 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: 60%;
- height: 2px;
- border-bottom: 2px solid ${p => p.theme.border};
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- }
- &::after {
- content: "";
- background-color: rgb(224, 220, 229);
- border-radius: 50%;
- height: 6px;
- width: 6px;
- position: absolute;
- left: 60%;
- 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: ${space(0.5)};
- text-overflow: ellipsis;
- white-space: nowrap;
- font-weight: bold;
- }
- .TraceEmDash {
- margin-left: ${space(0.5)};
- margin-right: ${space(0.5)};
- }
- .TraceDescription {
- white-space: nowrap;
- }
- `;
|