Compare commits

..

5 Commits

Author SHA1 Message Date
aks07
7991c321ff feat(trace-details): share metadata row across header and span summary 2026-07-01 02:26:02 +05:30
aks07
3385776cce feat(trace-details): rework span details panel
Extract the summary into a SpanSummary component, place it by panel width
(above the tabs when narrow, inside Overview when wide), single-scroll sticky
tabs, and fix service/status-message badge overflow + truncation.
2026-06-30 20:20:17 +05:30
aks07
f5286d69f6 fix(trace-details): single borders for docked span details 2026-06-30 20:20:15 +05:30
aks07
bcbac9a15c refactor(trace-details): give TraceIdField its own CSS module 2026-06-30 20:20:09 +05:30
aks07
43038c59b5 feat(trace-details): full-width span percentile panel with visible borders 2026-06-30 20:20:07 +05:30
16 changed files with 410 additions and 281 deletions

2
.github/CODEOWNERS vendored
View File

@@ -138,7 +138,7 @@ go.mod @therealpandey
/tests/integration/ @therealpandey
# e2e tests
/tests/e2e/ @SigNoz/frontend-maintainers
/tests/e2e/ @AshwinBhatkal
# Flagger Owners

View File

@@ -0,0 +1,9 @@
// A single metadata cell: non-interactive, so keep the default arrow cursor.
.item {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
cursor: default;
color: inherit;
}

View File

@@ -0,0 +1,32 @@
import { ReactNode } from 'react';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import { Typography } from '@signozhq/ui/typography';
import styles from './EntityMetadataItem.module.scss';
interface EntityMetadataItemProps {
tooltip: string;
icon?: ReactNode;
children: ReactNode;
}
function EntityMetadataItem({
tooltip,
icon,
children,
}: EntityMetadataItemProps): JSX.Element {
return (
<TooltipSimple title={tooltip}>
<span className={styles.item}>
{icon}
<Typography.Text as="span">{children}</Typography.Text>
</span>
</TooltipSimple>
);
}
EntityMetadataItem.defaultProps = {
icon: null,
};
export default EntityMetadataItem;

View File

@@ -0,0 +1,10 @@
.row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px 16px;
font-size: var(--periscope-font-size-base);
color: var(--l2-foreground);
// Keep Typography.Text at the row's size (it reads this var, not inherited font-size).
--typography-font-size: var(--periscope-font-size-base);
}

View File

@@ -0,0 +1,107 @@
import { CalendarClock, Server, Timer } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import cx from 'classnames';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import HttpStatusBadge from 'components/HttpStatusBadge/HttpStatusBadge';
import EntityMetadataItem from './EntityMetadataItem';
import styles from './EntityMetadataRow.module.scss';
interface EntityMetadataRowProps {
entity: 'trace' | 'span';
className?: string;
service?: { name: string; entryPoint?: string };
durationMs?: number;
execTimePercent?: number;
timestamp?: string;
statusCode?: string | number;
}
const ICON_SIZE = 14;
// Shared metadata row for the trace-details header and the span summary. Each
// node renders only when its data is provided; hovering shows a tooltip naming
// the value and keeps the default cursor. Interactive bits (e.g. linked spans)
// are intentionally kept out of here and rendered by the caller.
function EntityMetadataRow({
entity,
className,
service,
durationMs,
execTimePercent,
timestamp,
statusCode,
}: EntityMetadataRowProps): JSX.Element {
const entityLabel = entity === 'trace' ? 'Trace' : 'Span';
const durationTooltip =
entity === 'trace' ? 'Trace Duration' : 'Span Duration';
// Single source of duration formatting so both rows label units identically.
const duration =
durationMs != null
? getYAxisFormattedValue(`${durationMs}`, 'ms')
: undefined;
return (
<div className={cx(styles.row, className)}>
{service && (
<EntityMetadataItem
tooltip="Root service and entry-point span"
icon={<Server size={ICON_SIZE} />}
>
{service.name}
{service.entryPoint && (
<>
{' — '}
<Badge color="secondary" variant="outline">
{service.entryPoint}
</Badge>
</>
)}
</EntityMetadataItem>
)}
{duration && (
<EntityMetadataItem
tooltip={durationTooltip}
icon={<Timer size={ICON_SIZE} />}
>
{duration}
{execTimePercent != null && (
<>
{' — '}
<strong>{execTimePercent.toFixed(2)}%</strong>
{' of total exec time'}
</>
)}
</EntityMetadataItem>
)}
{timestamp && (
<EntityMetadataItem
tooltip={`${entityLabel} start time`}
icon={<CalendarClock size={ICON_SIZE} />}
>
{timestamp}
</EntityMetadataItem>
)}
{statusCode && (
<EntityMetadataItem tooltip="Root span status code">
<HttpStatusBadge statusCode={statusCode} />
</EntityMetadataItem>
)}
</div>
);
}
EntityMetadataRow.defaultProps = {
className: undefined,
service: undefined,
durationMs: undefined,
execTimePercent: undefined,
timestamp: undefined,
statusCode: undefined,
};
export default EntityMetadataRow;

View File

@@ -1,4 +1,4 @@
.root {
.panel {
height: 100%;
display: flex;
flex-direction: column;
@@ -9,13 +9,14 @@
}
}
.body {
.panelBody {
padding: 12px;
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-width: none;
background: var(--l1-background);
font-size: 14px;
@@ -33,14 +34,17 @@
min-width: 0;
}
// Single scroll: when the summary sits above (non-docked modes) it scrolls away
// and the tab section pins to the top; tab content scrolls inside `.tabsScroll`.
.tabsSection {
flex: 1;
min-width: 0;
max-height: 100%;
min-height: 400px;
display: flex;
flex-direction: column;
overflow: hidden;
position: sticky;
top: 0;
flex: 0 0 auto;
height: 100%;
// TabsRoot — direct child of tabs-section
> div {
@@ -75,79 +79,6 @@
}
}
.spanRow {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
justify-content: space-between;
}
.spanInfo {
display: flex;
flex-wrap: wrap;
gap: 4px 16px;
padding: 8px 0;
}
.spanInfoItem {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--l2-foreground);
white-space: nowrap;
}
.highlightedOptions {
padding: 8px 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0;
// KeyValueLabel uses a global `.key-value-label` root; constrain it
// inside the two-column grid so values can ellipsize cleanly.
:global(.key-value-label) {
width: auto;
min-width: 0;
overflow: hidden;
}
}
.serviceDot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-forest);
flex-shrink: 0;
}
.statusMessageBadge {
width: 100%;
min-width: 0;
box-sizing: border-box;
}
.traceId {
color: var(--accent-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
.traceIdCopy {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0;
height: auto;
justify-content: flex-start;
}
// Tooltip is rendered in a portal but the SpanDetailsPanel can be docked as a
// FloatingPanel (z-index 999), which would otherwise sit on top of the default
// tooltip (z-index 50). Bump the tooltip above the panel.

View File

@@ -5,20 +5,11 @@ import {
TabsRoot,
TabsTrigger,
} from '@signozhq/ui/tabs';
import {
Bookmark,
CalendarClock,
ChartColumnBig,
Link2,
List,
ScrollText,
Timer,
} from '@signozhq/icons';
import { Bookmark, ChartColumnBig, List, ScrollText } from '@signozhq/icons';
import { Skeleton } from 'antd';
import { DetailsHeader, DetailsPanelDrawer } from 'components/DetailsPanel';
import { HeaderAction } from 'components/DetailsPanel/DetailsHeader/DetailsHeader';
import { DetailsPanelState } from 'components/DetailsPanel/types';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import { QueryParams } from 'constants/query';
import {
initialQueryBuilderFormValuesMap,
@@ -41,14 +32,13 @@ import {
} from 'pages/TraceDetailsV3/utils';
import { DataViewer } from 'periscope/components/DataViewer';
import { FloatingPanel } from 'periscope/components/FloatingPanel';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { getLeafKeyFromPath } from 'periscope/components/PrettyView/utils';
import { useMeasure } from 'react-use';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { SpanV3 } from 'types/api/trace/getTraceV3';
import { DataSource, LogsAggregatorOperator } from 'types/common/queryBuilder';
import { openInNewTab } from 'utils/navigation';
import { HIGHLIGHTED_OPTIONS } from './config';
import {
// KEY_ATTRIBUTE_KEYS, // uncomment when key attributes section is re-enabled
SpanDetailVariant,
@@ -57,17 +47,10 @@ import {
import DockModeSwitcher from './DockModeSwitcher';
import { useSpanAttributeActions } from './hooks/useSpanAttributeActions';
import { useTracePinnedFields } from './hooks/useTracePinnedFields';
import {
LinkedSpansPanel,
LinkedSpansToggle,
useLinkedSpans,
} from './LinkedSpans/LinkedSpans';
import SpanPercentileBadge from './SpanPercentile/SpanPercentileBadge';
import SpanPercentilePanel from './SpanPercentile/SpanPercentilePanel';
import useSpanPercentile from './SpanPercentile/useSpanPercentile';
import Events from './Events/Events';
import SpanLogs from './SpanLogs/SpanLogs';
import { useSpanContextLogs } from './SpanLogs/useSpanContextLogs';
import SpanSummary from './SpanSummary';
import styles from './SpanDetailsPanel.module.scss';
@@ -80,6 +63,10 @@ interface SpanDetailsPanelProps {
traceEndTime?: number;
}
// At/above this panel width the summary moves inside the Overview tab (bottom
// dock, or a floating/right panel widened to match). ~right-dock max width.
const WIDE_PANEL_BREAKPOINT = 720;
function SpanDetailsContent({
selectedSpan,
traceStartTime,
@@ -90,6 +77,7 @@ function SpanDetailsContent({
traceEndTime?: number;
}): JSX.Element {
const FIVE_MINUTES_IN_MS = 5 * 60 * 1000;
const [bodyRef, { width: bodyWidth }] = useMeasure<HTMLDivElement>();
const spanAttributeActions = useSpanAttributeActions();
const logTraceEvent = useTraceDetailLogEvent('v3', selectedSpan.trace_id);
const handleTabChange = useCallback(
@@ -101,8 +89,6 @@ function SpanDetailsContent({
},
[logTraceEvent, selectedSpan.span_id],
);
const percentile = useSpanPercentile(selectedSpan);
const linkedSpans = useLinkedSpans((selectedSpan as any).references);
// One-time conversion of any V2-format value still living in the
// `span_details_pinned_attributes` user pref into V3 nested-path format.
@@ -281,113 +267,20 @@ function SpanDetailsContent({
// .map((key) => ({ key, value: allAttrs[key] }));
// }, [selectedSpan]);
// Width-driven: when the panel is wide, the summary moves inside the Overview
// tab; when narrow it stays above the tabs.
const isWide = bodyWidth >= WIDE_PANEL_BREAKPOINT;
const summary = (
<SpanSummary
selectedSpan={selectedSpan}
traceStartTime={traceStartTime}
traceEndTime={traceEndTime}
/>
);
return (
<div className={styles.body}>
<div className={styles.detailsSection}>
<div className={styles.spanRow}>
<KeyValueLabel
badgeKey="Span name"
badgeValue={selectedSpan.name}
maxCharacters={50}
/>
<SpanPercentileBadge
loading={percentile.loading}
percentileValue={percentile.percentileValue}
duration={percentile.duration}
spanPercentileData={percentile.spanPercentileData}
isOpen={percentile.isOpen}
toggleOpen={percentile.toggleOpen}
/>
</div>
<SpanPercentilePanel selectedSpan={selectedSpan} percentile={percentile} />
{/* Span info: exec time + start time */}
<div className={styles.spanInfo}>
<div className={styles.spanInfoItem}>
<Timer size={14} />
<span>
{getYAxisFormattedValue(`${selectedSpan.duration_nano / 1000000}`, 'ms')}
{traceStartTime && traceEndTime && traceEndTime > traceStartTime && (
<>
{' — '}
<strong>
{(
(selectedSpan.duration_nano * 100) /
((traceEndTime - traceStartTime) * 1e6)
).toFixed(2)}
%
</strong>
{' of total exec time'}
</>
)}
</span>
</div>
<div className={styles.spanInfoItem}>
<CalendarClock size={14} />
<span>
{dayjs(selectedSpan.timestamp).format('HH:mm:ss — MMM D, YYYY')}
</span>
</div>
<div className={styles.spanInfoItem}>
<Link2 size={14} />
<LinkedSpansToggle
count={linkedSpans.count}
isOpen={linkedSpans.isOpen}
toggleOpen={linkedSpans.toggleOpen}
/>
</div>
</div>
<LinkedSpansPanel
linkedSpans={linkedSpans.linkedSpans}
isOpen={linkedSpans.isOpen}
/>
{/* Step 6: HighlightedOptions */}
<div className={styles.highlightedOptions}>
{HIGHLIGHTED_OPTIONS.map((option) => {
const rendered = option.render(selectedSpan);
if (!rendered) {
return null;
}
return (
<KeyValueLabel
key={option.key}
badgeKey={option.label}
badgeValue={rendered}
direction="column"
/>
);
})}
</div>
{/* Step 7: KeyAttributes — commented out, pinning in PrettyView covers this.
{keyAttributes.length > 0 && (
<div className="span-details-panel__key-attributes">
<div className="span-details-panel__key-attributes-label">
KEY ATTRIBUTES
</div>
<div className="span-details-panel__key-attributes-chips">
{keyAttributes.map(({ key, value }) => (
<ActionMenu
key={key}
items={buildKeyAttrMenu(key, value)}
trigger={['click']}
placement="bottomRight"
>
<div>
<KeyValueLabel badgeKey={key} badgeValue={value} />
</div>
</ActionMenu>
))}
</div>
</div>
)}
*/}
{/* Step 8: MiniTraceContext */}
</div>
<div className={styles.panelBody} ref={bodyRef}>
{!isWide && <div className={styles.detailsSection}>{summary}</div>}
<div className={styles.tabsSection}>
{/* Step 9: ContentTabs */}
@@ -411,6 +304,7 @@ function SpanDetailsContent({
<div className={styles.tabsScroll}>
<TabsContent value="overview">
{isWide && summary}
<DataViewer
data={spanDisplayData}
drawerKey="trace-details"
@@ -535,7 +429,7 @@ function SpanDetailsPanel({
traceEndTime={traceEndTime}
/>
) : (
<div className={styles.body}>
<div className={styles.panelBody}>
<Skeleton active paragraph={{ rows: 6 }} title={{ width: '60%' }} />
</div>
)}
@@ -546,7 +440,7 @@ function SpanDetailsPanel({
variant === SpanDetailVariant.DOCKED ||
variant === SpanDetailVariant.DOCKED_RIGHT
) {
return <div className={styles.root}>{content}</div>;
return <div className={styles.panel}>{content}</div>;
}
if (variant === SpanDetailVariant.DRAWER) {
@@ -554,7 +448,7 @@ function SpanDetailsPanel({
<DetailsPanelDrawer
isOpen={panelState.isOpen}
onClose={panelState.close}
className={styles.root}
className={styles.panel}
>
{content}
</DetailsPanelDrawer>
@@ -564,7 +458,7 @@ function SpanDetailsPanel({
return (
<FloatingPanel
isOpen={panelState.isOpen}
className={styles.root}
className={styles.panel}
width={PANEL_WIDTH}
minWidth={480}
height={window.innerHeight - PANEL_MARGIN_TOP - PANEL_MARGIN_BOTTOM}

View File

@@ -3,11 +3,12 @@
display: flex;
flex-direction: column;
position: relative;
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
border-radius: 4px;
filter: drop-shadow(2px 4px 16px rgba(0, 0, 0, 0.2));
backdrop-filter: blur(20px);
margin: 8px 16px;
margin: 8px 0 0;
width: 100%;
}
.header {
@@ -16,7 +17,7 @@
justify-content: space-between;
gap: 8px;
padding: 8px;
border-bottom: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l2-border);
}
.content {
@@ -162,20 +163,20 @@
.resourceSelector {
overflow: hidden;
width: calc(100% + 16px);
width: 100%;
position: absolute;
top: 32px;
left: -8px;
left: 0;
z-index: 1000;
border-radius: 4px;
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
background: var(--l1-background);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
}
.resourceSelectorHeader {
border-bottom: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l2-border);
}
.resourceSelectorInput {

View File

@@ -0,0 +1,65 @@
.spanRow {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
justify-content: space-between;
}
// Metadata and the linked-spans line share one column so every line has the
// same vertical gap and line-height as the rows inside MetadataRow.
.spanMetaGroup {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px 0;
--typography-line-height: var(--line-height-20);
}
.spanInfoItem {
display: flex;
align-items: center;
gap: 6px;
font-size: var(--periscope-font-size-base);
line-height: var(--line-height-20);
color: var(--l2-foreground);
white-space: nowrap;
}
.highlightedOptions {
padding: 8px 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0;
// Constrain KeyValueLabel inside the grid so values can ellipsize cleanly.
:global(.key-value-label) {
width: auto;
min-width: 0;
overflow: hidden;
}
}
.serviceDot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-forest);
flex-shrink: 0;
}
// Badges stay fit-content so short values shrink and long ones truncate.
.serviceBadge,
.statusMessageBadge {
max-width: 100%;
min-width: 0;
}
// Truncating text inside a badge (service name, status message).
.badgeEllipsisText {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@@ -0,0 +1,103 @@
import { Link2 } from '@signozhq/icons';
import dayjs from 'dayjs';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { SpanV3 } from 'types/api/trace/getTraceV3';
import EntityMetadataRow from '../EntityMetadata/EntityMetadataRow';
import { HIGHLIGHTED_OPTIONS } from './config';
import {
LinkedSpansPanel,
LinkedSpansToggle,
useLinkedSpans,
} from './LinkedSpans/LinkedSpans';
import SpanPercentileBadge from './SpanPercentile/SpanPercentileBadge';
import SpanPercentilePanel from './SpanPercentile/SpanPercentilePanel';
import useSpanPercentile from './SpanPercentile/useSpanPercentile';
import styles from './SpanSummary.module.scss';
interface SpanSummaryProps {
selectedSpan: SpanV3;
traceStartTime?: number;
traceEndTime?: number;
}
// Summary block shown above (narrow) / beside (wide) the tabs: span name +
// percentile, exec time / timestamp / linked spans, and the highlighted options.
function SpanSummary({
selectedSpan,
traceStartTime,
traceEndTime,
}: SpanSummaryProps): JSX.Element {
const percentile = useSpanPercentile(selectedSpan);
const linkedSpans = useLinkedSpans((selectedSpan as any).references);
return (
<>
<div className={styles.spanRow}>
<KeyValueLabel
badgeKey="Span name"
badgeValue={selectedSpan.name}
maxCharacters={50}
/>
<SpanPercentileBadge
loading={percentile.loading}
percentileValue={percentile.percentileValue}
duration={percentile.duration}
spanPercentileData={percentile.spanPercentileData}
isOpen={percentile.isOpen}
toggleOpen={percentile.toggleOpen}
/>
</div>
<SpanPercentilePanel selectedSpan={selectedSpan} percentile={percentile} />
<div className={styles.spanMetaGroup}>
<EntityMetadataRow
entity="span"
durationMs={selectedSpan.duration_nano / 1000000}
execTimePercent={
traceStartTime && traceEndTime && traceEndTime > traceStartTime
? (selectedSpan.duration_nano * 100) /
((traceEndTime - traceStartTime) * 1e6)
: undefined
}
timestamp={dayjs(selectedSpan.timestamp).format('HH:mm:ss — MMM D, YYYY')}
/>
<div className={styles.spanInfoItem}>
<Link2 size={14} />
<LinkedSpansToggle
count={linkedSpans.count}
isOpen={linkedSpans.isOpen}
toggleOpen={linkedSpans.toggleOpen}
/>
</div>
</div>
<LinkedSpansPanel
linkedSpans={linkedSpans.linkedSpans}
isOpen={linkedSpans.isOpen}
/>
<div className={styles.highlightedOptions}>
{HIGHLIGHTED_OPTIONS.map((option) => {
const rendered = option.render(selectedSpan);
if (!rendered) {
return null;
}
return (
<KeyValueLabel
key={option.key}
badgeKey={option.label}
badgeValue={rendered}
direction="column"
/>
);
})}
</div>
</>
);
}
export default SpanSummary;

View File

@@ -0,0 +1,18 @@
.traceId {
color: var(--accent-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
.traceIdCopy {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0;
height: auto;
justify-content: flex-start;
}

View File

@@ -5,7 +5,7 @@ import { toast } from '@signozhq/ui/sonner';
import ROUTES from 'constants/routes';
import { SpanV3 } from 'types/api/trace/getTraceV3';
import styles from './SpanDetailsPanel.module.scss';
import styles from './TraceIdField.module.scss';
interface TraceIdFieldProps {
span: SpanV3;

View File

@@ -3,7 +3,7 @@ import { Badge } from '@signozhq/ui/badge';
import ExpandableValue from 'periscope/components/ExpandableValue';
import { SpanV3 } from 'types/api/trace/getTraceV3';
import styles from './SpanDetailsPanel.module.scss';
import styles from './SpanSummary.module.scss';
import { TraceIdField } from './TraceIdField';
interface HighlightedOption {
@@ -18,9 +18,11 @@ export const HIGHLIGHTED_OPTIONS: HighlightedOption[] = [
label: 'SERVICE',
render: (span): ReactNode | null =>
span['service.name'] ? (
<Badge color="vanilla">
<Badge color="vanilla" className={styles.serviceBadge}>
<span className={styles.serviceDot} />
{span['service.name']}
<span className={styles.badgeEllipsisText} title={span['service.name']}>
{span['service.name']}
</span>
</Badge>
) : null,
},
@@ -50,12 +52,8 @@ export const HIGHLIGHTED_OPTIONS: HighlightedOption[] = [
render: (span): ReactNode | null =>
span.status_message ? (
<ExpandableValue value={span.status_message} title="Status message">
<Badge
color="vanilla"
textEllipsis="end"
className={styles.statusMessageBadge}
>
{span.status_message}
<Badge color="vanilla" className={styles.statusMessageBadge}>
<span className={styles.badgeEllipsisText}>{span.status_message}</span>
</Badge>
</ExpandableValue>
) : null,

View File

@@ -61,25 +61,6 @@
color: var(--l2-foreground);
}
.subItem {
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.separator {
color: var(--l2-foreground);
opacity: 0.5;
}
.entryPointBadge {
padding: 2px 8px;
border: 1px solid var(--l2-border);
border-radius: 4px;
font-size: 12px;
}
.skeleton {
:global(.ant-skeleton-input) {
width: 160px !important;

View File

@@ -10,18 +10,10 @@ import {
import { Skeleton } from 'antd';
import cx from 'classnames';
import FieldsSelector from 'components/FieldsSelector';
import HttpStatusBadge from 'components/HttpStatusBadge/HttpStatusBadge';
import ROUTES from 'constants/routes';
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
import dayjs from 'dayjs';
import history, { hasInAppHistory } from 'lib/history';
import {
ArrowLeft,
CalendarClock,
ChartPie,
Server,
Timer,
} from '@signozhq/icons';
import { ArrowLeft, ChartPie } from '@signozhq/icons';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { TraceDetailV3URLProps } from 'types/api/trace/getTraceV3';
import { DataSource } from 'types/common/queryBuilder';
@@ -29,6 +21,7 @@ import { DataSource } from 'types/common/queryBuilder';
import { TraceDetailEventKeys, TraceDetailEvents } from '../events';
import { useTraceDetailLogEvent } from '../hooks/useTraceDetailLogEvent';
import { useTraceStore } from '../stores/traceStore';
import EntityMetadataRow from '../EntityMetadata/EntityMetadataRow';
import AnalyticsPanel from '../SpanDetailsPanel/AnalyticsPanel/AnalyticsPanel';
import Filters from '../TraceWaterfall/TraceWaterfallStates/Success/Filters/Filters';
import TraceOptionsMenu from './TraceOptionsMenu';
@@ -122,8 +115,6 @@ function TraceDetailsHeader({
const durationMs = traceMetadata
? traceMetadata.endTimestampMillis - traceMetadata.startTimestampMillis
: 0;
const { time: formattedDuration, timeUnitName } =
convertTimeToRelevantUnit(durationMs);
return (
<div className={styles.wrapper}>
@@ -200,29 +191,18 @@ function TraceDetailsHeader({
{showTraceDetails && (
<div className={styles.subHeader}>
{traceMetadata ? (
<>
<span className={styles.subItem}>
<Server size={13} />
{traceMetadata.rootServiceName}
<span className={styles.separator}></span>
<span className={styles.entryPointBadge}>
{traceMetadata.rootServiceEntryPoint}
</span>
</span>
<span className={styles.subItem}>
<Timer size={13} />
{parseFloat(formattedDuration.toFixed(2))} {timeUnitName}
</span>
<span className={styles.subItem}>
<CalendarClock size={13} />
{dayjs(traceMetadata.startTimestampMillis).format(
DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS,
)}
</span>
{traceMetadata.rootSpanStatusCode && (
<HttpStatusBadge statusCode={traceMetadata.rootSpanStatusCode} />
<EntityMetadataRow
entity="trace"
service={{
name: traceMetadata.rootServiceName,
entryPoint: traceMetadata.rootServiceEntryPoint,
}}
durationMs={durationMs}
timestamp={dayjs(traceMetadata.startTimestampMillis).format(
DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS,
)}
</>
statusCode={traceMetadata.rootSpanStatusCode}
/>
) : (
<DetailsLoader />
)}

View File

@@ -22,8 +22,8 @@
.rightDock {
display: flex;
flex-direction: column;
border-left: 1px solid var(--l2-border);
min-width: 0;
border-top: 1px solid var(--l2-border);
}
// Shared Ant Collapse chrome reset for both the flamegraph and waterfall