Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions web/oss/src/assets/custom-resize-handle.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,13 @@ th:hover .custom-resize-handle,
th:focus .custom-resize-handle {
opacity: 1;
}

/*
* Fixed-right columns (e.g. a sticky Actions column) anchor the handle inside
* the cell so the default 9px overhang doesn't bleed into the scrollbar gutter
* and create a phantom column slot on the right edge of the table.
*/
.ant-table-cell-fix-right .custom-resize-handle {
right: 0;
width: 9px;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {memo} from "react"

import {Space, Tooltip, Typography} from "antd"
import {useAtomValue} from "jotai"
import {Typography} from "antd"

import {SpanCategory} from "@/oss/services/tracing/types"
import {nodeDisplayNameAtomFamily} from "@/oss/state/newObservability"

import {spanTypeStyles} from "../assets/constants"

Expand All @@ -14,24 +12,17 @@ interface Props {
}

const NodeNameCell = memo(({name, type}: Props) => {
const display = useAtomValue(nodeDisplayNameAtomFamily(name))
const {icon: Icon} = spanTypeStyles[type ?? "undefined"]

return (
<Space align="center" size={4}>
<div className="grid place-items-center">
<div className="flex items-center gap-1 min-w-0">
<div className="grid place-items-center shrink-0">
<Icon size={16} />
</div>
<Typography>
{display.truncated ? (
<Tooltip title={display.full} placement="bottom">
{display.text}
</Tooltip>
) : (
display.text
)}
</Typography>
</Space>
<Typography.Text ellipsis={{tooltip: name}} className="flex-1 min-w-0">
{name}
</Typography.Text>
</div>
)
})

Expand Down
11 changes: 0 additions & 11 deletions web/oss/src/state/newObservability/atoms/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,6 @@ export const traceAnnotationInfoAtomFamily = atomFamily((key: string) =>
)

// Formatting helpers ----------------------------------------------------------
export const nodeDisplayNameAtomFamily = atomFamily((name: string) =>
atom(() => {
const truncated = name.length >= 15
return {
text: truncated ? `${name.slice(0, 15)}...` : name,
full: name,
truncated,
}
}),
)

export const formattedTimestampAtomFamily = atomFamily((ts?: string) =>
atom(() => formatDay({date: ts, outputFormat: "HH:mm:ss DD MMM YYYY"})),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,23 @@ const InfiniteVirtualTableInnerBase = <RecordType extends object>({
)

const typeChipFeature = useTypeChipFeature(typeChips)
const finalColumns = useTypeChipColumns(
const typeChipColumns = useTypeChipColumns(
resizableProcessedColumns,
dataSource,
typeChipFeature.typeChips,
)

// Workaround for an AntD virtual-table layout quirk: after fast horizontal
// scrolling, header <th> widths can drift away from body cell widths until
// *something* forces a column-level re-render. We bump `layoutNudge` after
// horizontal scroll settles to produce fresh column object references in
// `finalColumns`, which is enough to make AntD rebuild its layout state.
const [layoutNudge, setLayoutNudge] = useState(0)

const finalColumns = useMemo(
() => (layoutNudge > 0 ? typeChipColumns.map((col) => ({...col})) : typeChipColumns),
[typeChipColumns, layoutNudge],
)
const columnDescendantMap = useMemo(
() => buildColumnDescendantMap(resizableProcessedColumns),
[resizableProcessedColumns],
Expand Down Expand Up @@ -475,6 +487,31 @@ const InfiniteVirtualTableInnerBase = <RecordType extends object>({
visibilityRootRef.current = visibilityRoot ?? containerRef.current
}, [visibilityRoot])

// Bump layoutNudge after horizontal scroll settles. We only react to
// changes in scrollLeft so vertical scrolling (the common case) doesn't
// trigger an extra re-render on every pause.
useEffect(() => {
if (!scrollContainer) return
let timer: ReturnType<typeof setTimeout> | null = null
let lastScrollLeft = scrollContainer.scrollLeft

const onScroll = () => {
if (scrollContainer.scrollLeft === lastScrollLeft) return
lastScrollLeft = scrollContainer.scrollLeft
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
setLayoutNudge((prev) => prev + 1)
timer = null
}, 150)
}

scrollContainer.addEventListener("scroll", onScroll, {passive: true})
return () => {
scrollContainer.removeEventListener("scroll", onScroll)
if (timer) clearTimeout(timer)
}
}, [scrollContainer])

const mergedComponents = useMemo(() => {
if (!resizableHeaderComponents) {
return resolvedTableProps.components
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {memo, useEffect, useMemo, useState} from "react"
import {memo, useMemo, useState} from "react"
import type {ThHTMLAttributes} from "react"

import {Skeleton} from "antd"
Expand All @@ -22,28 +22,31 @@ export interface ResizableTitleProps extends Omit<
export const ResizableTitle = memo((props: ResizableTitleProps) => {
const {onResize, onResizeStart, onResizeStop, width, minWidth, ...restProps} = props

// Local live width to avoid forcing parent re-renders on every drag frame
const [liveWidth, setLiveWidth] = useState<number | undefined>(width)
// liveWidth is set only during an active drag so the <th> carries an inline
// width override for smooth visual feedback. When idle it's undefined and
// the cell is sized by AntD's <colgroup>, keeping header and body in sync.
const [liveWidth, setLiveWidth] = useState<number | undefined>(undefined)
const isDragging = liveWidth !== undefined

const resolvedMinWidth = useMemo(
() => (typeof minWidth === "number" ? minWidth : 48),
[minWidth],
)

useEffect(() => {
setLiveWidth(width)
}, [width])

// Only enable resizable behavior when a resize handler is provided.
// This ensures non-resizable columns (e.g., selection or fixed columns)
// are not wrapped in the Resizable component and keep their native layout.
// This ensures non-resizable columns (e.g., selection column) keep their
// native layout.
if (!width || !onResize) {
return <th {...restProps} />
}
return (
<Resizable
width={liveWidth ?? width}
height={0}
onResizeStart={(...args) => onResizeStart?.(...args)}
onResizeStart={(e, data) => {
setLiveWidth(width ?? data.size.width)
onResizeStart?.(e, data)
}}
handle={
<span
className="react-resizable-handle custom-resize-handle"
Expand All @@ -52,9 +55,14 @@ export const ResizableTitle = memo((props: ResizableTitleProps) => {
}
onResize={(e: React.SyntheticEvent, data: ResizeCallbackData) => {
setLiveWidth(data.size.width)
onResize && onResize(e, data)
onResize?.(e, data)
}}
onResizeStop={(e, data) => {
onResizeStop?.(e, data)
// Commit lives in the parent atom now — clear the drag override
// so subsequent renders source width from column.width via colgroup.
setLiveWidth(undefined)
}}
onResizeStop={(...args) => onResizeStop?.(...args)}
draggableOpts={{enableUserSelectHack: false}}
>
<th
Expand All @@ -63,7 +71,7 @@ export const ResizableTitle = memo((props: ResizableTitleProps) => {
...restProps.style,
paddingRight: 8,
minWidth: resolvedMinWidth,
width: (liveWidth ?? width) || resolvedMinWidth || 160,
...(isDragging ? {width: liveWidth} : null),
}}
className={cn([restProps.className, {"select-none": !!onResize}])}
>
Expand Down
Loading
Loading